Skip to content

Commit 67b3c2a

Browse files
committed
Add schema functionality to directives
1 parent 3b3df19 commit 67b3c2a

File tree

6 files changed

+104
-26
lines changed

6 files changed

+104
-26
lines changed
Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
import Foundation
22
import OrionProcessor
33

4-
// NOTE: These fixtures double as the glue file for Hooks.x.swift
4+
// NOTE: These fixtures double as the glue file for the runtime tests
55

6-
let engine = OrionDiagnosticEngine()
7-
engine.addConsumer(.printing)
6+
// we want to scope this so that the objects' deinits are called
7+
do {
8+
let engine = OrionDiagnosticEngine()
9+
engine.addConsumer(.printing)
810

9-
let orionTests = URL(fileURLWithPath: #file).deletingLastPathComponent().appendingPathComponent("../../Tests/OrionTests")
11+
let orionTests = URL(fileURLWithPath: #filePath)
12+
.deletingLastPathComponent()
13+
.appendingPathComponent("../../Tests/OrionTests")
1014

11-
let data = try OrionBatchParser(inputs: [orionTests], diagnosticEngine: engine).parse()
12-
let options = OrionGenerator.Options(emitSourceLocations: false)
13-
let generator = OrionGenerator(data: data, diagnosticEngine: engine, options: options)
14-
let glue = try generator.generate()
15+
let parserOptions = OrionParser.Options()
16+
let data = try OrionBatchParser(inputs: [orionTests], diagnosticEngine: engine, options: parserOptions).parse()
17+
let generatorOptions = OrionGenerator.Options(emitSourceLocations: false)
18+
let generator = OrionGenerator(data: data, diagnosticEngine: engine, options: generatorOptions)
19+
let glue = try generator.generate()
1520

16-
let dest = orionTests.appendingPathComponent("Generated.xc.swift")
17-
try glue.write(to: dest, atomically: true, encoding: .utf8)
21+
let dest = orionTests.appendingPathComponent("Generated.xc.swift")
22+
try glue.write(to: dest, atomically: true, encoding: .utf8)
1823

19-
print("Wrote glue file to \(dest.standardized.path)")
24+
print("Wrote glue file to \(dest.standardized.path)")
25+
}

Sources/OrionProcessor/OrionBatchParser.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,12 @@ public final class OrionBatchParser {
3333

3434
public let inputs: [URL]
3535
public let diagnosticEngine: OrionDiagnosticEngine
36+
public let options: OrionParser.Options
3637
// Note: inputs can include directories
37-
public init(inputs: [URL], diagnosticEngine: OrionDiagnosticEngine = .init()) {
38+
public init(inputs: [URL], diagnosticEngine: OrionDiagnosticEngine = .init(), options: OrionParser.Options = .init()) {
3839
self.inputs = inputs
3940
self.diagnosticEngine = diagnosticEngine
41+
self.options = options
4042
}
4143

4244
private func computeFiles() throws -> [URL] {
@@ -71,7 +73,7 @@ public final class OrionBatchParser {
7173
let files = try computeFiles().sorted { $0.path < $1.path }
7274
let engine = diagnosticEngine
7375
let allData = files.concurrentMap { file -> Result<OrionData, Error> in
74-
Result { try OrionParser(file: file, diagnosticEngine: engine).parse() }
76+
Result { try OrionParser(file: file, diagnosticEngine: engine, options: options).parse() }
7577
}
7678
return OrionData(merging: try allData.map { try $0.get() })
7779
}

Sources/OrionProcessor/OrionDirective.swift

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ struct OrionDirectiveDiagnostic: Error, LocalizedError {
7878

7979
final class OrionDirectiveParser {
8080
static let shared = OrionDirectiveParser()
81-
private static let prefix = "orion:"
81+
private static let prefix = "orion"
8282

8383
private let exactMap: [String: OrionDirective.Type]
8484
private let prefixMap: [String: OrionDirective.Type]
@@ -115,10 +115,29 @@ final class OrionDirectiveParser {
115115
return customList.lazy.compactMap { $0(name) }.first
116116
}
117117

118-
func directive(from text: String, at location: SourceLocation) throws -> OrionDirective? {
118+
func directive(from text: String, at location: SourceLocation, schema: Set<String> = []) throws -> OrionDirective? {
119119
guard text.hasPrefix(Self.prefix) else { return nil }
120120
let dropped = text.dropFirst(Self.prefix.count)
121-
let parts = dropped.split(separator: " ")
121+
// directives can be of the form `orion:foo` or `orion[mySchema]:foo`.
122+
// check which form it is, and if it's the latter then only parse
123+
// the directive if the schema is within our enabled schema set.
124+
let body: Substring // the `foo` bit
125+
switch dropped.first {
126+
case ":":
127+
body = dropped.dropFirst()
128+
case "[":
129+
let withoutOpening = dropped.dropFirst() // mySchema]:foo
130+
guard let endIdx = withoutOpening.firstIndex(of: "]")
131+
else { return nil }
132+
let textSchema = withoutOpening[..<endIdx] // mySchema
133+
guard schema.contains(String(textSchema)) else { return nil }
134+
let afterSchema = withoutOpening[endIdx...] // ]:foo
135+
guard afterSchema.hasPrefix("]:") else { return nil }
136+
body = afterSchema.dropFirst(2)
137+
default:
138+
return nil
139+
}
140+
let parts = body.split(separator: " ")
122141
guard let name = parts.first.map(String.init) else { return nil }
123142
let arguments = parts.dropFirst().map(String.init)
124143
guard let matched = type(matching: name) else {
@@ -147,7 +166,8 @@ enum OrionDirectives {
147166
ReturnsRetained.self,
148167
New.self,
149168
Disable.self,
150-
SuprTramp.self
169+
SuprTramp.self,
170+
IgnoreImport.self,
151171
]
152172

153173
struct ReturnsRetained: OrionDirective {
@@ -208,4 +228,16 @@ enum OrionDirectives {
208228
}
209229
}
210230
}
231+
232+
struct IgnoreImport: OrionDirective {
233+
static let matchRule: OrionDirectiveMatchRule = .exact("ignore_import")
234+
235+
let base: OrionDirectiveBase
236+
init(base: OrionDirectiveBase) throws {
237+
self.base = base
238+
guard base.arguments.isEmpty else {
239+
throw OrionDirectiveDiagnostic("ignore_import directive expected zero arguments, got \(base.arguments.count)")
240+
}
241+
}
242+
}
211243
}

Sources/OrionProcessor/OrionParser.swift

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@ import SwiftSyntax
33

44
public final class OrionParser {
55

6+
public struct Options {
7+
public let schema: Set<String>
8+
9+
public init(schema: Set<String> = []) {
10+
self.schema = schema
11+
}
12+
}
13+
614
private enum Source {
715
case file(URL)
816
case contents(String)
@@ -25,22 +33,25 @@ public final class OrionParser {
2533
}
2634

2735
public let engine: DiagnosticEngine
36+
public let options: Options
2837
private let source: Source
2938

30-
public init(file: URL, diagnosticEngine: OrionDiagnosticEngine = .init()) {
39+
public init(file: URL, diagnosticEngine: OrionDiagnosticEngine = .init(), options: Options = .init()) {
3140
source = .file(file)
3241
self.engine = diagnosticEngine.createEngine()
42+
self.options = options
3343
}
3444

35-
public init(contents: String, diagnosticEngine: OrionDiagnosticEngine = .init()) {
45+
public init(contents: String, diagnosticEngine: OrionDiagnosticEngine = .init(), options: Options = .init()) {
3646
source = .contents(contents)
3747
self.engine = diagnosticEngine.createEngine()
48+
self.options = options
3849
}
3950

4051
public func parse() throws -> OrionData {
4152
let syntax = try source.parseSyntax(diagnosticEngine: engine)
4253
let converter = SourceLocationConverter(file: source.filename, tree: syntax)
43-
let visitor = OrionVisitor(diagnosticEngine: engine, sourceLocationConverter: converter)
54+
let visitor = OrionVisitor(diagnosticEngine: engine, sourceLocationConverter: converter, options: options)
4455
visitor.walk(syntax)
4556
guard !visitor.didFail else {
4657
throw OrionFailure()

Sources/OrionProcessor/OrionVisitor.swift

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,17 @@ class OrionVisitor: SyntaxVisitor {
146146
"hookWillActivate", "hookDidActivate"
147147
]
148148

149+
let options: OrionParser.Options
149150
let converter: SourceLocationConverter
150151
let diagnosticEngine: DiagnosticEngine
151-
init(diagnosticEngine: DiagnosticEngine, sourceLocationConverter: SourceLocationConverter) {
152+
init(
153+
diagnosticEngine: DiagnosticEngine,
154+
sourceLocationConverter: SourceLocationConverter,
155+
options: OrionParser.Options
156+
) {
152157
self.diagnosticEngine = diagnosticEngine
153158
self.converter = sourceLocationConverter
159+
self.options = options
154160
}
155161

156162
private(set) var data = OrionData()
@@ -185,7 +191,7 @@ class OrionVisitor: SyntaxVisitor {
185191
return nil
186192
}
187193
do {
188-
return try OrionDirectiveParser.shared.directive(from: directive, at: location)
194+
return try OrionDirectiveParser.shared.directive(from: directive, at: location, schema: options.schema)
189195
} catch let err as OrionDirectiveDiagnostic {
190196
if warnOnFailure {
191197
diagnosticEngine.diagnose(err.diagnosticMessage, location: location)
@@ -601,6 +607,12 @@ class OrionVisitor: SyntaxVisitor {
601607
}
602608

603609
override func visitPost(_ node: ImportDeclSyntax) {
610+
let directives = makeDirectives(for: Syntax(node))
611+
let ignoreImports = directives.filter { $0 is OrionDirectives.IgnoreImport }
612+
guard ignoreImports.isEmpty else {
613+
ignoreImports.forEach { $0.setUsed() }
614+
return
615+
}
604616
data.imports.append(node.withoutTrivia())
605617
}
606618

Sources/OrionProcessorCLI/main.swift

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,17 @@ struct OrionCommand: ParsableCommand {
5353
)
5454
) var backendModules: [String] = []
5555

56+
@Option(
57+
help: ArgumentHelp(
58+
"Add name to schema.",
59+
discussion: """
60+
The schema list can be used to conditionally enable Orion directives. It is \
61+
empty by default. Read the documentation on directives for more information.
62+
""",
63+
valueName: "name"
64+
)
65+
) var schema: [String] = []
66+
5667
@Flag(
5768
inversion: .prefixedNo,
5869
help: ArgumentHelp(
@@ -85,6 +96,8 @@ struct OrionCommand: ParsableCommand {
8596
let engine = OrionDiagnosticEngine()
8697
engine.addConsumer(.printing)
8798

99+
let parserOptions = OrionParser.Options(schema: Set(schema))
100+
88101
let data: OrionData
89102
if inputs == ["-"] {
90103
var input = ""
@@ -94,23 +107,25 @@ struct OrionCommand: ParsableCommand {
94107
}
95108
let parser = OrionParser(
96109
contents: input,
97-
diagnosticEngine: engine
110+
diagnosticEngine: engine,
111+
options: parserOptions
98112
)
99113
data = try parser.parse()
100114
} else {
101115
let parser = OrionBatchParser(
102116
inputs: inputs.map(URL.init(fileURLWithPath:)),
103-
diagnosticEngine: engine
117+
diagnosticEngine: engine,
118+
options: parserOptions
104119
)
105120
data = try parser.parse()
106121
}
107122

108-
let options = OrionGenerator.Options(
123+
let generatorOptions = OrionGenerator.Options(
109124
backend: backend,
110125
extraBackendModules: Set(backendModules),
111126
emitSourceLocations: sourceLocations
112127
)
113-
let generator = OrionGenerator(data: data, diagnosticEngine: engine, options: options)
128+
let generator = OrionGenerator(data: data, diagnosticEngine: engine, options: generatorOptions)
114129
let out = try generator.generate()
115130
if let output = output {
116131
let outputURL = URL(fileURLWithPath: output)

0 commit comments

Comments
 (0)