diff --git a/Documentation/Development.md b/Documentation/Development.md index efba654d4..8773f051f 100644 --- a/Documentation/Development.md +++ b/Documentation/Development.md @@ -1,5 +1,31 @@ # Developing `swift-format` +## Keeping the Pipeline and Tests Updated + +Since Swift does not yet have a runtime reflection system, we use code +generation to keep the linting/formatting pipeline up-to-date. If you add or +remove any rules from the `SwiftFormatRules` module, or if you add or remove +any `visit` methods from an existing rule in that module, you must run the +`generate-pipeline` tool update the pipeline and configuration sources. + +The easiest way to do this is to run the following command in your terminal: + +```shell +swift run generate-pipeline +``` + +If successful, this tool will update +`Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift` and +`Sources/SwiftFormat/Pipelines+Generated.swift`. + +Likewise, you should keep the Linux XCTest manifests updated if you add or +remove any tests from `swift-format` by running the following command in your +terminal: + +```shell +swift test --generate-linuxmain +``` + ## Command Line Options for Debugging `swift-format` provides some hidden command line options to facilitate diff --git a/Package.swift b/Package.swift index 2c5540744..32d64e1c4 100644 --- a/Package.swift +++ b/Package.swift @@ -48,12 +48,10 @@ let package = Package( .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftOperators", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), - ], - plugins: ["generate-pipeline-plugin"] + ] ), .target( - name: "SwiftFormatConfiguration", - plugins: ["generate-pipeline-plugin"] + name: "SwiftFormatConfiguration" ), .target( name: "SwiftFormatCore", @@ -65,8 +63,7 @@ let package = Package( ), .target( name: "SwiftFormatRules", - dependencies: ["SwiftFormatCore", "SwiftFormatConfiguration"], - plugins: ["generate-pipeline-plugin"] + dependencies: ["SwiftFormatCore", "SwiftFormatConfiguration"] ), .target( name: "SwiftFormatPrettyPrint", @@ -93,22 +90,15 @@ let package = Package( ] ), - .plugin( - name: "generate-pipeline-plugin", - capability: .buildTool(), - dependencies: [ - "generate-pipeline" - ] - ), .executableTarget( name: "generate-pipeline", dependencies: [ - .product(name: "ArgumentParser", package: "swift-argument-parser"), + "SwiftFormatCore", + "SwiftFormatRules", .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), ] ), - .executableTarget( name: "swift-format", dependencies: [ diff --git a/Plugins/generate-pipeline-plugin/GeneratePipelinePlugin.swift b/Plugins/generate-pipeline-plugin/GeneratePipelinePlugin.swift deleted file mode 100644 index 457b79d27..000000000 --- a/Plugins/generate-pipeline-plugin/GeneratePipelinePlugin.swift +++ /dev/null @@ -1,73 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -import PackagePlugin - -/// The name of the Swift file to generate for a particular target. -let targetGeneratedSourceMapping = [ - "SwiftFormat": "Pipelines+Generated.swift", - "SwiftFormatConfiguration": "RuleRegistry+Generated.swift", - "SwiftFormatRules": "RuleNameCache+Generated.swift", -] - -/// A Swift Package Manager build tool that runs `generate-pipeline` to generate the format/lint -/// pipelines from the current state of the rules. -@main -struct GeneratePipelinePlugin: BuildToolPlugin { - enum Error: Swift.Error, CustomStringConvertible { - /// The plugin was applied to a target that isn't supported. - case notApplicableToTarget(String) - - var description: String { - switch self { - case .notApplicableToTarget(let target): - return """ - 'generate-pipeline-plugin' cannot be applied to '\(target)'; \ - supported targets are \(targetGeneratedSourceMapping.keys) - """ - } - } - } - - func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] { - guard let generatedSourceName = targetGeneratedSourceMapping[target.name] else { - throw Error.notApplicableToTarget(target.name) - } - - let generatePipelineTool = try context.tool(named: "generate-pipeline") - let sourcesDir = context.package.directory.appending("Sources") - let outputFile = context.pluginWorkDirectory - .appending("GeneratedSources") - .appending(generatedSourceName) - - let rulesSources = - (try context.package.targets(named: ["SwiftFormatRules"]).first as? SwiftSourceModuleTarget)? - .sourceFiles.map(\.path) ?? [] - - return [ - .buildCommand( - displayName: "Generating \(generatedSourceName) for \(target.name)", - executable: generatePipelineTool.path, - arguments: [ - "--sources-directory", - sourcesDir.string, - "--output-file", - outputFile.string, - "--target", - target.name, - ], - inputFiles: rulesSources, - outputFiles: [outputFile] - ) - ] - } -} diff --git a/Sources/SwiftFormat/Pipelines+Generated.swift b/Sources/SwiftFormat/Pipelines+Generated.swift new file mode 100644 index 000000000..f2e9f8f72 --- /dev/null +++ b/Sources/SwiftFormat/Pipelines+Generated.swift @@ -0,0 +1,331 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +// This file is automatically generated with generate-pipeline. Do Not Edit! + +import SwiftFormatCore +import SwiftFormatRules +import SwiftSyntax + +/// A syntax visitor that delegates to individual rules for linting. +/// +/// This file will be extended with `visit` methods in Pipelines+Generated.swift. +class LintPipeline: SyntaxVisitor { + + /// The formatter context. + let context: Context + + /// Stores lint and format rule instances, indexed by the `ObjectIdentifier` of a rule's + /// class type. + var ruleCache = [ObjectIdentifier: Rule]() + + /// Creates a new lint pipeline. + init(context: Context) { + self.context = context + super.init(viewMode: .sourceAccurate) + } + + override func visit(_ node: AsExprSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(NeverForceUnwrap.visit, for: node) + return .visitChildren + } + + override func visit(_ node: AssociatedtypeDeclSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) + visitIfEnabled(NoLeadingUnderscores.visit, for: node) + return .visitChildren + } + + override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) + visitIfEnabled(AlwaysUseLowerCamelCase.visit, for: node) + visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) + visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node) + visitIfEnabled(NoLeadingUnderscores.visit, for: node) + visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) + return .visitChildren + } + + override func visit(_ node: ClosureSignatureSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(AlwaysUseLowerCamelCase.visit, for: node) + visitIfEnabled(ReturnVoidInsteadOfEmptyTuple.visit, for: node) + return .visitChildren + } + + override func visit(_ node: CodeBlockItemListSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(DoNotUseSemicolons.visit, for: node) + visitIfEnabled(OneVariableDeclarationPerLine.visit, for: node) + visitIfEnabled(UseEarlyExits.visit, for: node) + return .visitChildren + } + + override func visit(_ node: CodeBlockSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(AmbiguousTrailingClosureOverload.visit, for: node) + return .visitChildren + } + + override func visit(_ node: ConditionElementSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(NoParensAroundConditions.visit, for: node) + return .visitChildren + } + + override func visit(_ node: DeinitializerDeclSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) + visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) + visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) + return .visitChildren + } + + override func visit(_ node: EnumCaseElementSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(AlwaysUseLowerCamelCase.visit, for: node) + visitIfEnabled(NoLeadingUnderscores.visit, for: node) + return .visitChildren + } + + override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) + visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node) + visitIfEnabled(FullyIndirectEnum.visit, for: node) + visitIfEnabled(NoLeadingUnderscores.visit, for: node) + visitIfEnabled(OneCasePerLine.visit, for: node) + visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) + return .visitChildren + } + + override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node) + visitIfEnabled(NoAccessLevelOnExtensionDeclaration.visit, for: node) + visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) + return .visitChildren + } + + override func visit(_ node: ForInStmtSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(UseWhereClausesInForLoops.visit, for: node) + return .visitChildren + } + + override func visit(_ node: ForcedValueExprSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(NeverForceUnwrap.visit, for: node) + return .visitChildren + } + + override func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(NoEmptyTrailingClosureParentheses.visit, for: node) + visitIfEnabled(OnlyOneTrailingClosureArgument.visit, for: node) + return .visitChildren + } + + override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) + visitIfEnabled(AlwaysUseLowerCamelCase.visit, for: node) + visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) + visitIfEnabled(NoLeadingUnderscores.visit, for: node) + visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) + visitIfEnabled(ValidateDocumentationComments.visit, for: node) + return .visitChildren + } + + override func visit(_ node: FunctionParameterSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(NoLeadingUnderscores.visit, for: node) + return .visitChildren + } + + override func visit(_ node: FunctionSignatureSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(NoVoidReturnOnFunctionSignature.visit, for: node) + return .visitChildren + } + + override func visit(_ node: FunctionTypeSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(ReturnVoidInsteadOfEmptyTuple.visit, for: node) + return .visitChildren + } + + override func visit(_ node: GenericParameterSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(NoLeadingUnderscores.visit, for: node) + return .visitChildren + } + + override func visit(_ node: IdentifierPatternSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(IdentifiersMustBeASCII.visit, for: node) + visitIfEnabled(NoLeadingUnderscores.visit, for: node) + return .visitChildren + } + + override func visit(_ node: IfStmtSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(NoParensAroundConditions.visit, for: node) + return .visitChildren + } + + override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) + visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) + visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) + visitIfEnabled(ValidateDocumentationComments.visit, for: node) + return .visitChildren + } + + override func visit(_ node: IntegerLiteralExprSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(GroupNumericLiterals.visit, for: node) + return .visitChildren + } + + override func visit(_ node: MemberDeclBlockSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(AmbiguousTrailingClosureOverload.visit, for: node) + return .visitChildren + } + + override func visit(_ node: MemberDeclListSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(DoNotUseSemicolons.visit, for: node) + return .visitChildren + } + + override func visit(_ node: OptionalBindingConditionSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(AlwaysUseLowerCamelCase.visit, for: node) + return .visitChildren + } + + override func visit(_ node: PatternBindingSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(UseSingleLinePropertyGetter.visit, for: node) + return .visitChildren + } + + override func visit(_ node: PrecedenceGroupDeclSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(NoLeadingUnderscores.visit, for: node) + return .visitChildren + } + + override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) + visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) + visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node) + visitIfEnabled(NoLeadingUnderscores.visit, for: node) + visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) + return .visitChildren + } + + override func visit(_ node: RepeatWhileStmtSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(NoParensAroundConditions.visit, for: node) + return .visitChildren + } + + override func visit(_ node: SimpleTypeIdentifierSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(UseShorthandTypeNames.visit, for: node) + return .visitChildren + } + + override func visit(_ node: SourceFileSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(AlwaysUseLowerCamelCase.visit, for: node) + visitIfEnabled(AmbiguousTrailingClosureOverload.visit, for: node) + visitIfEnabled(FileScopedDeclarationPrivacy.visit, for: node) + visitIfEnabled(NeverForceUnwrap.visit, for: node) + visitIfEnabled(NeverUseForceTry.visit, for: node) + visitIfEnabled(NeverUseImplicitlyUnwrappedOptionals.visit, for: node) + visitIfEnabled(OrderedImports.visit, for: node) + return .visitChildren + } + + override func visit(_ node: SpecializeExprSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(UseShorthandTypeNames.visit, for: node) + return .visitChildren + } + + override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) + visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) + visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node) + visitIfEnabled(NoLeadingUnderscores.visit, for: node) + visitIfEnabled(UseSynthesizedInitializer.visit, for: node) + visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) + return .visitChildren + } + + override func visit(_ node: SubscriptDeclSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) + visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) + visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) + return .visitChildren + } + + override func visit(_ node: SwitchCaseLabelSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(NoLabelsInCasePatterns.visit, for: node) + return .visitChildren + } + + override func visit(_ node: SwitchCaseListSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(NoCasesWithOnlyFallthrough.visit, for: node) + return .visitChildren + } + + override func visit(_ node: SwitchStmtSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(NoParensAroundConditions.visit, for: node) + return .visitChildren + } + + override func visit(_ node: TokenSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(NoBlockComments.visit, for: node) + return .visitChildren + } + + override func visit(_ node: TryExprSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(NeverUseForceTry.visit, for: node) + return .visitChildren + } + + override func visit(_ node: TypealiasDeclSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) + visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) + visitIfEnabled(NoLeadingUnderscores.visit, for: node) + visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) + return .visitChildren + } + + override func visit(_ node: ValueBindingPatternSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(UseLetInEveryBoundCaseVariable.visit, for: node) + return .visitChildren + } + + override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { + visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) + visitIfEnabled(AlwaysUseLowerCamelCase.visit, for: node) + visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) + visitIfEnabled(NeverUseImplicitlyUnwrappedOptionals.visit, for: node) + visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) + return .visitChildren + } +} + +extension FormatPipeline { + + func visit(_ node: Syntax) -> Syntax { + var node = node + node = DoNotUseSemicolons(context: context).visit(node) + node = FileScopedDeclarationPrivacy(context: context).visit(node) + node = FullyIndirectEnum(context: context).visit(node) + node = GroupNumericLiterals(context: context).visit(node) + node = NoAccessLevelOnExtensionDeclaration(context: context).visit(node) + node = NoCasesWithOnlyFallthrough(context: context).visit(node) + node = NoEmptyTrailingClosureParentheses(context: context).visit(node) + node = NoLabelsInCasePatterns(context: context).visit(node) + node = NoParensAroundConditions(context: context).visit(node) + node = NoVoidReturnOnFunctionSignature(context: context).visit(node) + node = OneCasePerLine(context: context).visit(node) + node = OneVariableDeclarationPerLine(context: context).visit(node) + node = OrderedImports(context: context).visit(node) + node = ReturnVoidInsteadOfEmptyTuple(context: context).visit(node) + node = UseEarlyExits(context: context).visit(node) + node = UseShorthandTypeNames(context: context).visit(node) + node = UseSingleLinePropertyGetter(context: context).visit(node) + node = UseTripleSlashForDocumentationComments(context: context).visit(node) + node = UseWhereClausesInForLoops(context: context).visit(node) + return node + } +} diff --git a/Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift b/Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift new file mode 100644 index 000000000..776990d1e --- /dev/null +++ b/Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift @@ -0,0 +1,52 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +// This file is automatically generated with generate-pipeline. Do Not Edit! + +enum RuleRegistry { + static let rules: [String: Bool] = [ + "AllPublicDeclarationsHaveDocumentation": false, + "AlwaysUseLowerCamelCase": true, + "AmbiguousTrailingClosureOverload": true, + "BeginDocumentationCommentWithOneLineSummary": false, + "DoNotUseSemicolons": true, + "DontRepeatTypeInStaticProperties": true, + "FileScopedDeclarationPrivacy": true, + "FullyIndirectEnum": true, + "GroupNumericLiterals": true, + "IdentifiersMustBeASCII": true, + "NeverForceUnwrap": false, + "NeverUseForceTry": false, + "NeverUseImplicitlyUnwrappedOptionals": false, + "NoAccessLevelOnExtensionDeclaration": true, + "NoBlockComments": true, + "NoCasesWithOnlyFallthrough": true, + "NoEmptyTrailingClosureParentheses": true, + "NoLabelsInCasePatterns": true, + "NoLeadingUnderscores": false, + "NoParensAroundConditions": true, + "NoVoidReturnOnFunctionSignature": true, + "OneCasePerLine": true, + "OneVariableDeclarationPerLine": true, + "OnlyOneTrailingClosureArgument": true, + "OrderedImports": true, + "ReturnVoidInsteadOfEmptyTuple": true, + "UseEarlyExits": false, + "UseLetInEveryBoundCaseVariable": true, + "UseShorthandTypeNames": true, + "UseSingleLinePropertyGetter": true, + "UseSynthesizedInitializer": true, + "UseTripleSlashForDocumentationComments": true, + "UseWhereClausesInForLoops": false, + "ValidateDocumentationComments": false, + ] +} diff --git a/Sources/SwiftFormatCore/Rule.swift b/Sources/SwiftFormatCore/Rule.swift index e5fe4140e..264bafd82 100644 --- a/Sources/SwiftFormatCore/Rule.swift +++ b/Sources/SwiftFormatCore/Rule.swift @@ -22,13 +22,6 @@ public protocol Rule { static var ruleName: String { get } /// Whether this rule is opt-in, meaning it is disabled by default. - /// - /// This property is scanned when swift-format is built in order to determine the correct default - /// values to generate for the `Configuration` object. As such, there are restrictions on its - /// form when overridden by rule classes. A rule class that overrides this implement this property - /// as a computed property whose getter returns exactly `true` or `false`, with no other logic. - /// The `return` statement may be explicitly provided or the property can use a single-statement - /// implicit return. static var isOptIn: Bool { get } /// Creates a new Rule in a given context. @@ -65,9 +58,9 @@ extension Rule { } context.findingEmitter.emit( - message, - category: RuleBasedFindingCategory(ruleType: type(of: self)), - location: syntaxLocation.flatMap(Finding.Location.init), - notes: notes) + message, + category: RuleBasedFindingCategory(ruleType: type(of: self)), + location: syntaxLocation.flatMap(Finding.Location.init), + notes: notes) } } diff --git a/Sources/SwiftFormatRules/RuleNameCache+Generated.swift b/Sources/SwiftFormatRules/RuleNameCache+Generated.swift new file mode 100644 index 000000000..56f5c3a12 --- /dev/null +++ b/Sources/SwiftFormatRules/RuleNameCache+Generated.swift @@ -0,0 +1,51 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +// This file is automatically generated with generate-pipeline. Do Not Edit! + +/// By default, the `Rule.ruleName` should be the name of the implementing rule type. +public let ruleNameCache: [ObjectIdentifier: String] = [ + ObjectIdentifier(AllPublicDeclarationsHaveDocumentation.self): "AllPublicDeclarationsHaveDocumentation", + ObjectIdentifier(AlwaysUseLowerCamelCase.self): "AlwaysUseLowerCamelCase", + ObjectIdentifier(AmbiguousTrailingClosureOverload.self): "AmbiguousTrailingClosureOverload", + ObjectIdentifier(BeginDocumentationCommentWithOneLineSummary.self): "BeginDocumentationCommentWithOneLineSummary", + ObjectIdentifier(DoNotUseSemicolons.self): "DoNotUseSemicolons", + ObjectIdentifier(DontRepeatTypeInStaticProperties.self): "DontRepeatTypeInStaticProperties", + ObjectIdentifier(FileScopedDeclarationPrivacy.self): "FileScopedDeclarationPrivacy", + ObjectIdentifier(FullyIndirectEnum.self): "FullyIndirectEnum", + ObjectIdentifier(GroupNumericLiterals.self): "GroupNumericLiterals", + ObjectIdentifier(IdentifiersMustBeASCII.self): "IdentifiersMustBeASCII", + ObjectIdentifier(NeverForceUnwrap.self): "NeverForceUnwrap", + ObjectIdentifier(NeverUseForceTry.self): "NeverUseForceTry", + ObjectIdentifier(NeverUseImplicitlyUnwrappedOptionals.self): "NeverUseImplicitlyUnwrappedOptionals", + ObjectIdentifier(NoAccessLevelOnExtensionDeclaration.self): "NoAccessLevelOnExtensionDeclaration", + ObjectIdentifier(NoBlockComments.self): "NoBlockComments", + ObjectIdentifier(NoCasesWithOnlyFallthrough.self): "NoCasesWithOnlyFallthrough", + ObjectIdentifier(NoEmptyTrailingClosureParentheses.self): "NoEmptyTrailingClosureParentheses", + ObjectIdentifier(NoLabelsInCasePatterns.self): "NoLabelsInCasePatterns", + ObjectIdentifier(NoLeadingUnderscores.self): "NoLeadingUnderscores", + ObjectIdentifier(NoParensAroundConditions.self): "NoParensAroundConditions", + ObjectIdentifier(NoVoidReturnOnFunctionSignature.self): "NoVoidReturnOnFunctionSignature", + ObjectIdentifier(OneCasePerLine.self): "OneCasePerLine", + ObjectIdentifier(OneVariableDeclarationPerLine.self): "OneVariableDeclarationPerLine", + ObjectIdentifier(OnlyOneTrailingClosureArgument.self): "OnlyOneTrailingClosureArgument", + ObjectIdentifier(OrderedImports.self): "OrderedImports", + ObjectIdentifier(ReturnVoidInsteadOfEmptyTuple.self): "ReturnVoidInsteadOfEmptyTuple", + ObjectIdentifier(UseEarlyExits.self): "UseEarlyExits", + ObjectIdentifier(UseLetInEveryBoundCaseVariable.self): "UseLetInEveryBoundCaseVariable", + ObjectIdentifier(UseShorthandTypeNames.self): "UseShorthandTypeNames", + ObjectIdentifier(UseSingleLinePropertyGetter.self): "UseSingleLinePropertyGetter", + ObjectIdentifier(UseSynthesizedInitializer.self): "UseSynthesizedInitializer", + ObjectIdentifier(UseTripleSlashForDocumentationComments.self): "UseTripleSlashForDocumentationComments", + ObjectIdentifier(UseWhereClausesInForLoops.self): "UseWhereClausesInForLoops", + ObjectIdentifier(ValidateDocumentationComments.self): "ValidateDocumentationComments", +] diff --git a/Sources/generate-pipeline/FileGenerator.swift b/Sources/generate-pipeline/FileGenerator.swift index 640477c57..bd4c8b32a 100644 --- a/Sources/generate-pipeline/FileGenerator.swift +++ b/Sources/generate-pipeline/FileGenerator.swift @@ -22,7 +22,12 @@ protocol FileGenerator { extension FileGenerator { /// Generates a file at the given URL, overwriting it if it already exists. func generateFile(at url: URL) throws { - FileManager.default.createFile(atPath: url.path, contents: nil, attributes: nil) + let fm = FileManager.default + if fm.fileExists(atPath: url.path) { + try fm.removeItem(at: url) + } + + fm.createFile(atPath: url.path, contents: nil, attributes: nil) let handle = try FileHandle(forWritingTo: url) defer { handle.closeFile() } diff --git a/Sources/generate-pipeline/GeneratePipelines.swift b/Sources/generate-pipeline/GeneratePipelines.swift deleted file mode 100644 index ca1aa3824..000000000 --- a/Sources/generate-pipeline/GeneratePipelines.swift +++ /dev/null @@ -1,57 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -import ArgumentParser -import Foundation -import SwiftSyntax - -/// The swift-format target for which sources should be generated. -enum Target: String, ExpressibleByArgument { - case swiftFormat = "SwiftFormat" - case swiftFormatConfiguration = "SwiftFormatConfiguration" - case swiftFormatRules = "SwiftFormatRules" -} - -@main -struct GeneratePipelines: ParsableCommand { - @Option(help: "The path to the swift-format package's 'Sources' directory.") - var sourcesDirectory: String - - @Option(help: "The path to the output file that should contain the generated source.") - var outputFile: String - - @Option(help: "The swift-format target for which sources should be generated.") - var target: Target - - func run() throws { - let sourcesDirectoryURL = URL(fileURLWithPath: sourcesDirectory) - let outputFileURL = URL(fileURLWithPath: outputFile) - - let rulesDirectory = sourcesDirectoryURL.appendingPathComponent("SwiftFormatRules") - let ruleCollector = RuleCollector() - try ruleCollector.collect(from: rulesDirectory) - - switch target { - case .swiftFormat: - // Generate a file with extensions for the lint and format pipelines. - try PipelineGenerator(ruleCollector: ruleCollector).generateFile(at: outputFileURL) - - case .swiftFormatConfiguration: - // Generate the rule registry dictionary for configuration. - try RuleRegistryGenerator(ruleCollector: ruleCollector).generateFile(at: outputFileURL) - - case .swiftFormatRules: - // Generate the rule name cache. - try RuleNameCacheGenerator(ruleCollector: ruleCollector).generateFile(at: outputFileURL) - } - } -} diff --git a/Sources/generate-pipeline/RuleCollector.swift b/Sources/generate-pipeline/RuleCollector.swift index 7977f2ba1..8f6a48774 100644 --- a/Sources/generate-pipeline/RuleCollector.swift +++ b/Sources/generate-pipeline/RuleCollector.swift @@ -11,28 +11,12 @@ //===----------------------------------------------------------------------===// import Foundation +import SwiftFormatCore import SwiftSyntax import SwiftParser /// Collects information about rules in the formatter code base. final class RuleCollector { - enum Error: Swift.Error, CustomStringConvertible { - /// Indicates that an `isOptIn` property was found when scanning a rule, but the property was - /// not defined correctly. - case invalidOptIn(ruleName: String) - - var description: String { - switch self { - case .invalidOptIn(let ruleName): - return """ - The 'isOptIn' property on the rule '\(ruleName)' was defined incorrectly. It must be a \ - computed static or class property with a single statement or expression that returns \ - 'true' or 'false'. - """ - } - } - } - /// Information about a detected rule. struct DetectedRule: Hashable { /// The type name of the rule. @@ -77,7 +61,7 @@ final class RuleCollector { let sourceFile = try Parser.parse(source: fileInput) for statement in sourceFile.statements { - guard let detectedRule = try self.detectedRule(at: statement) else { continue } + guard let detectedRule = self.detectedRule(at: statement) else { continue } if detectedRule.canFormat { // Format rules just get added to their own list; we run them each over the entire tree in @@ -97,7 +81,7 @@ final class RuleCollector { } /// Determine the rule kind for the declaration in the given statement, if any. - private func detectedRule(at statement: CodeBlockItemSyntax) throws -> DetectedRule? { + private func detectedRule(at statement: CodeBlockItemSyntax) -> DetectedRule? { let typeName: String let members: MemberDeclListSyntax let maybeInheritanceClause: TypeInheritanceClauseSyntax? @@ -148,109 +132,17 @@ final class RuleCollector { visitedNodes.append(firstType.name.text) } - // Ignore it if it doesn't have any; there's no point in putting no-op rules in the pipeline. - // Otherwise, return it (we don't need to look at the rest of the inheritances). + /// Ignore it if it doesn't have any; there's no point in putting no-op rules in the pipeline. + /// Otherwise, return it (we don't need to look at the rest of the inheritances). guard !visitedNodes.isEmpty else { return nil } - - // Determine if the rule is opt-in. The `isOptIn` property must be a computed property with a - // simple statement returning `true` or `false`. - guard let isOptIn = isRuleOptIn(members: members) else { - throw Error.invalidOptIn(ruleName: typeName) + guard let ruleType = _typeByName("SwiftFormatRules.\(typeName)") as? Rule.Type else { + preconditionFailure("Failed to find type for rule named \(typeName)") } return DetectedRule( typeName: typeName, visitedNodes: visitedNodes, canFormat: canFormat, - isOptIn: isOptIn) + isOptIn: ruleType.isOptIn) } return nil } - - /// Searches for a static/class property named `isOptIn` and returns its simple Boolean return - /// value, if possible. - /// - /// Returns `false` if no `isOptIn` property was found, or `nil` if a property was found but we - /// were unable to determine its simple Boolean return value. - private func isRuleOptIn(members: MemberDeclListSyntax) -> Bool? { - for member in members { - guard let varDecl = member.decl.as(VariableDeclSyntax.self) else { continue } - for binding in varDecl.bindings { - guard - let identifier = binding.pattern.as(IdentifierPatternSyntax.self), - identifier.identifier.text == "isOptIn" - else { - continue - } - - // Make sure that we've found a static/class property with a getter that returns a Boolean - // literal. Otherwise, return nil to indicate that the property found was invalid (and thus - // likely a programmer error). - guard - varDecl.modifiers?.contains(where: isStaticOrClassModifier) == true, - let getterBlock = computedGetter(for: binding), - let returnValue = booleanReturnValue(for: getterBlock) - else { - return nil - } - - return returnValue - } - } - - // If we didn't find an `isOptIn` property, return the default (false). - return false - } - - /// Returns the computed property getter for the given pattern binding, or `nil` if no getter was - /// found. - private func computedGetter(for binding: PatternBindingSyntax) -> CodeBlockSyntax? { - guard let accessor = binding.accessor else { return nil } - - if let implicitGetBlock = accessor.as(CodeBlockSyntax.self) { - // If the accessor was just a code block, then it's the implicit getter. - return implicitGetBlock - } - - if let accessorBlock = accessor.as(AccessorBlockSyntax.self) { - // If we have a list of accessors, find the getter by searching the list. - for childAccessor in accessorBlock.accessors { - if childAccessor.accessorKind.tokenKind == .contextualKeyword("get"), - let getBlock = childAccessor.body - { - return getBlock - } - } - return nil - } - - return nil - } - - /// Returns the simple Boolean literal return value of the given code block, or `nil` if it was - /// not possible to determine (for example, the code block had multiple statements or returned - /// something other than a Boolean literal). - private func booleanReturnValue(for codeBlock: CodeBlockSyntax) -> Bool? { - guard let statement = codeBlock.statements.firstAndOnly else { return nil } - - if let returnStatement = statement.item.as(ReturnStmtSyntax.self) { - // If it's a `return` statement, check for a Boolean literal value. - if let booleanExpr = returnStatement.expression?.as(BooleanLiteralExprSyntax.self) { - return booleanExpr.booleanLiteral.tokenKind == .trueKeyword - } - - return nil - } - - if let booleanExpr = statement.item.as(BooleanLiteralExprSyntax.self) { - // It's a Boolean literal expression with implicit return. - return booleanExpr.booleanLiteral.tokenKind == .trueKeyword - } - - return nil - } - - /// Returns a value indicating whether the given modifier is the `static` or `class` keyword. - private func isStaticOrClassModifier(_ modifier: DeclModifierSyntax) -> Bool { - let kind = modifier.name.tokenKind - return kind == .staticKeyword || kind == .classKeyword - } } diff --git a/Sources/generate-pipeline/main.swift b/Sources/generate-pipeline/main.swift new file mode 100644 index 000000000..afc469ad9 --- /dev/null +++ b/Sources/generate-pipeline/main.swift @@ -0,0 +1,44 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Foundation +import SwiftSyntax + +let sourcesDirectory = URL(fileURLWithPath: #file) + .deletingLastPathComponent() + .deletingLastPathComponent() +let rulesDirectory = sourcesDirectory.appendingPathComponent("SwiftFormatRules") +let pipelineFile = sourcesDirectory + .appendingPathComponent("SwiftFormat") + .appendingPathComponent("Pipelines+Generated.swift") +let ruleRegistryFile = sourcesDirectory + .appendingPathComponent("SwiftFormatConfiguration") + .appendingPathComponent("RuleRegistry+Generated.swift") + +let ruleNameCacheFile = sourcesDirectory + .appendingPathComponent("SwiftFormatRules") + .appendingPathComponent("RuleNameCache+Generated.swift") + +var ruleCollector = RuleCollector() +try ruleCollector.collect(from: rulesDirectory) + +// Generate a file with extensions for the lint and format pipelines. +let pipelineGenerator = PipelineGenerator(ruleCollector: ruleCollector) +try pipelineGenerator.generateFile(at: pipelineFile) + +// Generate the rule registry dictionary for configuration. +let registryGenerator = RuleRegistryGenerator(ruleCollector: ruleCollector) +try registryGenerator.generateFile(at: ruleRegistryFile) + +// Generate the rule name cache. +let ruleNameCacheGenerator = RuleNameCacheGenerator(ruleCollector: ruleCollector) +try ruleNameCacheGenerator.generateFile(at: ruleNameCacheFile)