Skip to content

Commit 09ed6c4

Browse files
Add support for configurable comments in generated files (#773)
### Motivation Users want the ability to add custom comments to generated files. One concrete use case is adding directives like `swift-format-ignore-file` and `swiftlint:disable all` to prevent these tools from processing generated code. ### Modifications - Added `additionalFileComments` property to `Config` and `UserConfig` structs - Modified `FileTranslator` to include additional comments along with the do-not-edit comment - Added CLI support with `--additional-file-comment` option - Added config tests to confirm the option propagates and the default is empty - Added snippet tests to validate the actual rendering - Updated documentation with examples and usage instructions ### Result Users can now configure additional comments to be added to generated files using either the config file or a command line option. Fixes #738. ### Test plan - Added unit tests for `Config` - Added snippet tests for rendering - Manual tests of generator using config and CLI on real OpenAPI doc: ```console % swift run swift-openapi-generator generate openapi-documents/petstore.yaml \ --mode types \ --output-directory test-output-cli \ --additional-file-comment "hello world" \ --additional-file-comment "testing, testing, 1, 2, 3" Build of product 'swift-openapi-generator' complete! (3.27s) Swift OpenAPI Generator is running with the following configuration: - OpenAPI document path: /Users/Si/work/code/swift-openapi-workspace/packages/swift-openapi-generator/openapi-documents/petstore.yaml - Configuration path: <none> - Generator modes: types - Access modifier: internal - Naming strategy: defensive - Name overrides: <none> - Feature flags: <none> - Output file names: Types.swift - Output directory: /Users/Si/work/code/swift-openapi-workspace/packages/swift-openapi-generator/test-output-cli - Diagnostics output path: <none - logs to stderr> - Current directory: /Users/Si/work/code/swift-openapi-workspace/packages/swift-openapi-generator - Plugin source: <none> - Is dry run: false - Additional imports: <none> - Additional file comments: hello world, testing, testing, 1, 2, 3 Writing data to file Types.swift... % head -5 test-output-cli/Types.swift // Generated by swift-openapi-generator, do not modify. // hello world // testing, testing, 1, 2, 3 @_spi(Generated) import OpenAPIRuntime ```
1 parent 0bd6d84 commit 09ed6c4

File tree

11 files changed

+109
-4
lines changed

11 files changed

+109
-4
lines changed

Sources/_OpenAPIGeneratorCore/Config.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ public struct Config: Sendable {
4646
/// Additional imports to add to each generated file.
4747
public var additionalImports: [String]
4848

49+
/// Additional comments to add to the top of each generated file.
50+
public var additionalFileComments: [String]
51+
4952
/// Filter to apply to the OpenAPI document before generation.
5053
public var filter: DocumentFilter?
5154

@@ -68,6 +71,7 @@ public struct Config: Sendable {
6871
/// - mode: The mode to use for generation.
6972
/// - access: The access modifier to use for generated declarations.
7073
/// - additionalImports: Additional imports to add to each generated file.
74+
/// - additionalFileComments: Additional comments to add to the top of each generated file.
7175
/// - filter: Filter to apply to the OpenAPI document before generation.
7276
/// - namingStrategy: The naming strategy to use for deriving Swift identifiers from OpenAPI identifiers.
7377
/// Defaults to `defensive`.
@@ -78,6 +82,7 @@ public struct Config: Sendable {
7882
mode: GeneratorMode,
7983
access: AccessModifier,
8084
additionalImports: [String] = [],
85+
additionalFileComments: [String] = [],
8186
filter: DocumentFilter? = nil,
8287
namingStrategy: NamingStrategy,
8388
nameOverrides: [String: String] = [:],
@@ -86,6 +91,7 @@ public struct Config: Sendable {
8691
self.mode = mode
8792
self.access = access
8893
self.additionalImports = additionalImports
94+
self.additionalFileComments = additionalFileComments
8995
self.filter = filter
9096
self.namingStrategy = namingStrategy
9197
self.nameOverrides = nameOverrides

Sources/_OpenAPIGeneratorCore/Translator/ClientTranslator/ClientTranslator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ struct ClientFileTranslator: FileTranslator {
3232

3333
let doc = parsedOpenAPI
3434

35-
let topComment: Comment = .inline(Constants.File.topComment)
35+
let topComment = self.topComment
3636

3737
let imports =
3838
Constants.File.clientServerImports + config.additionalImports.map { ImportDescription(moduleName: $0) }

Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ extension FileTranslator {
8383
)
8484
return TranslatorContext(safeNameGenerator: overridingGenerator)
8585
}
86+
87+
/// Creates a top comment that includes the default "do not modify" comment
88+
/// and any additional file comments from the configuration.
89+
var topComment: Comment {
90+
.inline(([Constants.File.topComment] + config.additionalFileComments).joined(separator: "\n"))
91+
}
8692
}
8793

8894
/// A set of configuration values for concrete file translators.

Sources/_OpenAPIGeneratorCore/Translator/ServerTranslator/ServerTranslator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ struct ServerFileTranslator: FileTranslator {
3030

3131
let doc = parsedOpenAPI
3232

33-
let topComment: Comment = .inline(Constants.File.topComment)
33+
let topComment = self.topComment
3434

3535
let imports =
3636
Constants.File.clientServerImports + config.additionalImports.map { ImportDescription(moduleName: $0) }

Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/TypesFileTranslator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ struct TypesFileTranslator: FileTranslator {
3232

3333
let doc = parsedOpenAPI
3434

35-
let topComment: Comment = .inline(Constants.File.topComment)
35+
let topComment = self.topComment
3636

3737
let imports = Constants.File.imports + config.additionalImports.map { ImportDescription(moduleName: $0) }
3838

Sources/swift-openapi-generator/Documentation.docc/Articles/Configuring-the-generator.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ The configuration file has the following keys:
3535
- `package`: Generated API is accessible from other modules within the same package or project.
3636
- `internal` (default): Generated API is accessible from the containing module only.
3737
- `additionalImports` (optional): array of strings. Each string value is a Swift module name. An import statement will be added to the generated source files for each module.
38+
- `additionalFileComments` (optional): array of strings. Each string value is a comment that will be added to the top of each generated file (after the do-not-edit comment). Useful for adding directives like `swift-format-ignore-file` or `swiftlint:disable all`.
3839
- `filter` (optional): Filters to apply to the OpenAPI document before generation.
3940
- `operations`: Operations with these operation IDs will be included in the filter.
4041
- `tags`: Operations tagged with these tags will be included in the filter.
@@ -95,6 +96,18 @@ additionalImports:
9596
accessModifier: public
9697
```
9798

99+
To add file comments to exclude generated files from formatting tools:
100+
101+
```yaml
102+
generate:
103+
- types
104+
- client
105+
namingStrategy: idiomatic
106+
additionalFileComments:
107+
- "swift-format-ignore-file"
108+
- "swiftlint:disable all"
109+
```
110+
98111
### Document filtering
99112

100113
The generator supports filtering the OpenAPI document prior to generation, which can be useful when

Sources/swift-openapi-generator/GenerateOptions+runGenerator.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ extension _GenerateOptions {
3232
let sortedModes = try resolvedModes(config)
3333
let resolvedAccessModifier = resolvedAccessModifier(config)
3434
let resolvedAdditionalImports = resolvedAdditionalImports(config)
35+
let resolvedAdditionalFileComments = resolvedAdditionalFileComments(config)
3536
let resolvedNamingStragy = resolvedNamingStrategy(config)
3637
let resolvedNameOverrides = resolvedNameOverrides(config)
3738
let resolvedFeatureFlags = resolvedFeatureFlags(config)
@@ -40,6 +41,7 @@ extension _GenerateOptions {
4041
mode: $0,
4142
access: resolvedAccessModifier,
4243
additionalImports: resolvedAdditionalImports,
44+
additionalFileComments: resolvedAdditionalFileComments,
4345
filter: config?.filter,
4446
namingStrategy: resolvedNamingStragy,
4547
nameOverrides: resolvedNameOverrides,
@@ -67,6 +69,7 @@ extension _GenerateOptions {
6769
- Plugin source: \(pluginSource?.rawValue ?? "<none>")
6870
- Is dry run: \(isDryRun)
6971
- Additional imports: \(resolvedAdditionalImports.isEmpty ? "<none>" : resolvedAdditionalImports.joined(separator: ", "))
72+
- Additional file comments: \(resolvedAdditionalFileComments.isEmpty ? "<none>" : resolvedAdditionalFileComments.joined(separator: ", "))
7073
"""
7174
)
7275
do {

Sources/swift-openapi-generator/GenerateOptions.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ struct _GenerateOptions: ParsableArguments {
4040

4141
@Option(help: "Additional import to add to all generated files.") var additionalImport: [String] = []
4242

43+
@Option(help: "Additional file comment to add to all generated files.") var additionalFileComment: [String] = []
44+
4345
@Option(help: "Pre-release feature to enable. Options: \(FeatureFlag.prettyListing).") var featureFlag:
4446
[FeatureFlag] = []
4547

@@ -81,6 +83,17 @@ extension _GenerateOptions {
8183
return []
8284
}
8385

86+
/// Returns a list of additional file comments requested by the user.
87+
/// - Parameter config: The configuration specified by the user.
88+
/// - Returns: A list of additional file comments requested by the user.
89+
func resolvedAdditionalFileComments(_ config: _UserConfig?) -> [String] {
90+
if !additionalFileComment.isEmpty { return additionalFileComment }
91+
if let additionalFileComments = config?.additionalFileComments, !additionalFileComments.isEmpty {
92+
return additionalFileComments
93+
}
94+
return []
95+
}
96+
8497
/// Returns the naming strategy requested by the user.
8598
/// - Parameter config: The configuration specified by the user.
8699
/// - Returns: The naming strategy requestd by the user.

Sources/swift-openapi-generator/UserConfig.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ struct _UserConfig: Codable {
3030
/// generated Swift file.
3131
var additionalImports: [String]?
3232

33+
/// A list of additional comments that are added to the top of every
34+
/// generated Swift file.
35+
var additionalFileComments: [String]?
36+
3337
/// Filter to apply to the OpenAPI document before generation.
3438
var filter: DocumentFilter?
3539

@@ -51,6 +55,7 @@ struct _UserConfig: Codable {
5155
case generate
5256
case accessModifier
5357
case additionalImports
58+
case additionalFileComments
5459
case filter
5560
case namingStrategy
5661
case nameOverrides

Tests/OpenAPIGeneratorCoreTests/Test_Config.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,17 @@ import OpenAPIKit
1717

1818
final class Test_Config: Test_Core {
1919
func testDefaultAccessModifier() { XCTAssertEqual(Config.defaultAccessModifier, .internal) }
20+
func testAdditionalFileComments() {
21+
let config = Config(
22+
mode: .types,
23+
access: .public,
24+
additionalFileComments: ["swift-format-ignore-file", "swiftlint:disable all"],
25+
namingStrategy: .defensive
26+
)
27+
XCTAssertEqual(config.additionalFileComments, ["swift-format-ignore-file", "swiftlint:disable all"])
28+
}
29+
func testEmptyAdditionalFileComments() {
30+
let config = Config(mode: .types, access: .public, namingStrategy: .defensive)
31+
XCTAssertEqual(config.additionalFileComments, [])
32+
}
2033
}

Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5862,8 +5862,42 @@ final class SnippetBasedReferenceTests: XCTestCase {
58625862
"""
58635863
)
58645864
}
5865-
}
58665865

5866+
func testAdditionalFileComments() throws {
5867+
let additionalFileComments = ["hello world", "foo bar baz"]
5868+
let config = Config(
5869+
mode: .types,
5870+
access: .private,
5871+
additionalImports: [],
5872+
additionalFileComments: additionalFileComments,
5873+
filter: nil,
5874+
namingStrategy: .idiomatic,
5875+
nameOverrides: [:]
5876+
)
5877+
let translator = TypesFileTranslator(
5878+
config: config,
5879+
diagnostics: XCTestDiagnosticCollector(test: self),
5880+
components: OpenAPI.Components()
5881+
)
5882+
let documentYAML = """
5883+
openapi: 3.1.0
5884+
info:
5885+
title: Minimal API
5886+
version: 1.0.0
5887+
paths: {}
5888+
"""
5889+
let document = try YAMLDecoder().decode(OpenAPI.Document.self, from: documentYAML)
5890+
let translation = try translator.translateFile(parsedOpenAPI: document)
5891+
try XCTAssertSwiftEquivalent(
5892+
XCTUnwrap(translation.file.contents.topComment),
5893+
"""
5894+
// Generated by swift-openapi-generator, do not modify.
5895+
// hello world
5896+
// foo bar baz
5897+
"""
5898+
)
5899+
}
5900+
}
58675901
extension SnippetBasedReferenceTests {
58685902
func makeTypesTranslator(openAPIDocumentYAML: String) throws -> TypesFileTranslator {
58695903
let document = try YAMLDecoder().decode(OpenAPI.Document.self, from: openAPIDocumentYAML)
@@ -6245,6 +6279,18 @@ private func XCTAssertSwiftEquivalent(
62456279
try XCTAssertEqualWithDiff(contents, expectedSwift, file: file, line: line)
62466280
}
62476281

6282+
private func XCTAssertSwiftEquivalent(
6283+
_ comment: _OpenAPIGeneratorCore.Comment,
6284+
_ expectedSwift: String,
6285+
file: StaticString = #filePath,
6286+
line: UInt = #line
6287+
) throws {
6288+
let renderer = TextBasedRenderer.default
6289+
renderer.renderComment(comment)
6290+
let contents = renderer.renderedContents()
6291+
try XCTAssertEqualWithDiff(contents, expectedSwift, file: file, line: line)
6292+
}
6293+
62486294
private func diff(expected: String, actual: String) throws -> String {
62496295
let process = Process()
62506296
process.executableURL = try resolveExecutable("bash")

0 commit comments

Comments
 (0)