@@ -74,13 +74,13 @@ public struct ModuleDependency: Hashable, Sendable, SerializableCodable {
74
74
public struct ModuleDependenciesContext : Sendable , SerializableCodable {
75
75
public var validate : BooleanWarningLevel
76
76
public var validateUnused : BooleanWarningLevel
77
- var moduleDependencies : [ ModuleDependency ]
77
+ public var declared : [ ModuleDependency ]
78
78
var fixItContext : FixItContext ?
79
79
80
- init ( validate: BooleanWarningLevel , validateUnused: BooleanWarningLevel , moduleDependencies : [ ModuleDependency ] , fixItContext: FixItContext ? = nil ) {
80
+ init ( validate: BooleanWarningLevel , validateUnused: BooleanWarningLevel , declared : [ ModuleDependency ] , fixItContext: FixItContext ? = nil ) {
81
81
self . validate = validate
82
82
self . validateUnused = validateUnused
83
- self . moduleDependencies = moduleDependencies
83
+ self . declared = declared
84
84
self . fixItContext = fixItContext
85
85
}
86
86
@@ -90,7 +90,7 @@ public struct ModuleDependenciesContext: Sendable, SerializableCodable {
90
90
guard validate != . no || validateUnused != . no else { return nil }
91
91
let downgrade = settings. globalScope. evaluate ( BuiltinMacros . VALIDATE_DEPENDENCIES_DOWNGRADE_ERRORS)
92
92
let fixItContext = validate != . no ? ModuleDependenciesContext . FixItContext ( settings: settings) : nil
93
- self . init ( validate: downgrade ? . yes : validate, validateUnused: validateUnused, moduleDependencies : settings. moduleDependencies, fixItContext: fixItContext)
93
+ self . init ( validate: downgrade ? . yes : validate, validateUnused: validateUnused, declared : settings. moduleDependencies, fixItContext: fixItContext)
94
94
}
95
95
96
96
public func makeUnsupportedToolchainDiagnostic( ) -> Diagnostic {
@@ -111,14 +111,14 @@ public struct ModuleDependenciesContext: Sendable, SerializableCodable {
111
111
if fromSwift && $0. importLocations. isEmpty { return false }
112
112
113
113
// TODO: if the difference is just the access modifier, we emit a new entry, but ultimately our fixit should update the existing entry or emit an error about a conflict
114
- if moduleDependencies . contains ( $0. 0 ) { return false }
114
+ if declared . contains ( $0. 0 ) { return false }
115
115
return true
116
116
}
117
117
}
118
118
119
119
public func computeUnusedDependencies( usedModuleNames: Set < String > ) -> [ ModuleDependency ] {
120
120
guard validateUnused != . no else { return [ ] }
121
- return moduleDependencies . filter { !$0. optional && !usedModuleNames. contains ( $0. name) }
121
+ return declared . filter { !$0. optional && !usedModuleNames. contains ( $0. name) }
122
122
}
123
123
124
124
/// Make diagnostics for missing module dependencies.
@@ -231,6 +231,7 @@ public struct ModuleDependenciesContext: Sendable, SerializableCodable {
231
231
public struct HeaderDependency : Hashable , Sendable , SerializableCodable {
232
232
public let name : String
233
233
public let accessLevel : AccessLevel
234
+ public let optional : Bool
234
235
235
236
public enum AccessLevel : String , Hashable , Sendable , CaseIterable , Codable , Serializable {
236
237
case Private = " private "
@@ -245,29 +246,25 @@ public struct HeaderDependency: Hashable, Sendable, SerializableCodable {
245
246
}
246
247
}
247
248
248
- public init ( name: String , accessLevel: AccessLevel ) {
249
+ public init ( name: String , accessLevel: AccessLevel , optional : Bool ) {
249
250
self . name = name
250
251
self . accessLevel = accessLevel
252
+ self . optional = optional
251
253
}
252
254
253
255
public init ( entry: String ) throws {
254
- var it = entry. split ( separator: " " ) . makeIterator ( )
255
- switch ( it. next ( ) , it. next ( ) , it. next ( ) ) {
256
- case ( let . some( name) , nil , nil ) :
257
- self . name = String ( name)
258
- self . accessLevel = . Private
259
-
260
- case ( let . some( accessLevel) , let . some( name) , nil ) :
261
- self . name = String ( name)
262
- self . accessLevel = try AccessLevel ( String ( accessLevel) )
263
-
264
- default :
265
- throw StubError . error ( " expected 1 or 2 space-separated components in: \( entry) " )
256
+ let re = #/((?<accessLevel>private|package|public)\s+)?(?<name>.+)(?<optional>\?)?/#
257
+ guard let match = entry. wholeMatch ( of: re) else {
258
+ throw StubError . error ( " Invalid header dependency format: \( entry) , expected [private|package|public] <name>[?] " )
266
259
}
260
+
261
+ self . name = String ( match. output. name)
262
+ self . accessLevel = try match. output. accessLevel. map { try AccessLevel ( String ( $0) ) } ?? . Private
263
+ self . optional = match. output. optional != nil
267
264
}
268
265
269
266
public var asBuildSettingEntry : String {
270
- " \( accessLevel == . Private ? " " : " \( accessLevel. rawValue) " ) \( name) "
267
+ " \( accessLevel == . Private ? " " : " \( accessLevel. rawValue) " ) \( name) \( optional ? " ? " : " " ) "
271
268
}
272
269
273
270
public var asBuildSettingEntryQuotedIfNeeded : String {
@@ -278,63 +275,104 @@ public struct HeaderDependency: Hashable, Sendable, SerializableCodable {
278
275
279
276
public struct HeaderDependenciesContext : Sendable , SerializableCodable {
280
277
public var validate : BooleanWarningLevel
281
- var headerDependencies : [ HeaderDependency ]
278
+ public var validateUnused : BooleanWarningLevel
279
+ public var declared : [ HeaderDependency ]
282
280
var fixItContext : FixItContext ?
283
281
284
- init ( validate: BooleanWarningLevel , headerDependencies : [ HeaderDependency ] , fixItContext: FixItContext ? = nil ) {
282
+ init ( validate: BooleanWarningLevel , validateUnused : BooleanWarningLevel , declared : [ HeaderDependency ] , fixItContext: FixItContext ? = nil ) {
285
283
self . validate = validate
286
- self . headerDependencies = headerDependencies
284
+ self . validateUnused = validateUnused
285
+ self . declared = declared
287
286
self . fixItContext = fixItContext
288
287
}
289
288
290
289
public init ? ( settings: Settings ) {
291
290
let validate = settings. globalScope. evaluate ( BuiltinMacros . VALIDATE_HEADER_DEPENDENCIES)
292
- guard validate != . no else { return nil }
291
+ let validateUnused = settings. globalScope. evaluate ( BuiltinMacros . VALIDATE_UNUSED_HEADER_DEPENDENCIES)
292
+ guard validate != . no || validateUnused != . no else { return nil }
293
293
let downgrade = settings. globalScope. evaluate ( BuiltinMacros . VALIDATE_DEPENDENCIES_DOWNGRADE_ERRORS)
294
294
let fixItContext = HeaderDependenciesContext . FixItContext ( settings: settings)
295
- self . init ( validate: downgrade ? . yes : validate, headerDependencies: settings. headerDependencies, fixItContext: fixItContext)
295
+ self . init ( validate: downgrade ? . yes : validate, validateUnused: validateUnused, declared: settings. headerDependencies, fixItContext: fixItContext)
296
+ }
297
+
298
+ public func makeUnsupportedToolchainDiagnostic( ) -> Diagnostic {
299
+ Diagnostic (
300
+ behavior: . warning,
301
+ location: . unknown,
302
+ data: DiagnosticData ( " The current toolchain does not support \( BuiltinMacros . VALIDATE_HEADER_DEPENDENCIES. name) " ) )
303
+ }
304
+
305
+ /// Compute missing module dependencies.
306
+ public func computeMissingAndUnusedDependencies( includes: [ Path ] ) -> ( missing: [ HeaderDependency ] , unused: [ HeaderDependency ] ) {
307
+ let declaredNames = Set ( declared. map { $0. name } )
308
+
309
+ let missing : [ HeaderDependency ]
310
+ if validate != . no {
311
+ missing = includes. filter { file in
312
+ return !declaredNames. contains ( where: { file. ends ( with: $0) } )
313
+ } . map {
314
+ // TODO: What if the basename doesn't uniquely identify the header?
315
+ HeaderDependency ( name: $0. basename, accessLevel: . Private, optional: false )
316
+ }
317
+ }
318
+ else {
319
+ missing = [ ]
320
+ }
321
+
322
+ let unused : [ HeaderDependency ]
323
+ if validateUnused != . no {
324
+ unused = declared. filter { !$0. optional && !declaredNames. contains ( $0. name) }
325
+ }
326
+ else {
327
+ unused = [ ]
328
+ }
329
+
330
+ return ( missing, unused)
296
331
}
297
332
298
333
/// Make diagnostics for missing header dependencies.
299
334
///
300
335
/// The compiler tracing information does not provide the include locations or whether they are public imports
301
336
/// (which depends on whether the import is in an installed header file).
302
- /// If `includes` is nil, the current toolchain does support the feature to trace imports.
303
- public func makeDiagnostics( includes: [ Path ] ? ) -> [ Diagnostic ] {
304
- guard validate != . no else { return [ ] }
305
- guard let includes else {
306
- return [ Diagnostic (
307
- behavior: . warning,
308
- location: . unknown,
309
- data: DiagnosticData ( " The current toolchain does not support \( BuiltinMacros . VALIDATE_HEADER_DEPENDENCIES. name) " ) ) ]
310
- }
311
-
312
- let headerDependencyNames = headerDependencies. map { $0. name }
313
- let missingDeps = includes. filter { file in
314
- return !headerDependencyNames. contains ( where: { file. ends ( with: $0) } )
315
- } . map {
316
- // TODO: What if the basename doesn't uniquely identify the header?
317
- HeaderDependency ( name: $0. basename, accessLevel: . Private)
318
- }
337
+ public func makeDiagnostics( missingDependencies: [ HeaderDependency ] , unusedDependencies: [ HeaderDependency ] ) -> [ Diagnostic ] {
338
+ let missingDiagnostics : [ Diagnostic ]
339
+ if !missingDependencies. isEmpty {
340
+ let behavior : Diagnostic . Behavior = validate == . yesError ? . error : . warning
319
341
320
- guard !missingDeps. isEmpty else { return [ ] }
342
+ let fixIt = fixItContext? . makeFixIt ( newHeaders: missingDependencies)
343
+ let fixIts = fixIt. map { [ $0] } ?? [ ]
321
344
322
- let behavior : Diagnostic . Behavior = validate == . yesError ? . error : . warning
345
+ let message = " Missing entries in \( BuiltinMacros . HEADER_DEPENDENCIES . name ) : \( missingDependencies . map { $0 . asBuildSettingEntryQuotedIfNeeded } . sorted ( ) . joined ( separator : " " ) ) "
323
346
324
- let fixIt = fixItContext? . makeFixIt ( newHeaders: missingDeps)
325
- let fixIts = fixIt. map { [ $0] } ?? [ ]
347
+ let location : Diagnostic . Location = fixIt. map {
348
+ Diagnostic . Location. path ( $0. sourceRange. path, line: $0. sourceRange. endLine, column: $0. sourceRange. endColumn)
349
+ } ?? Diagnostic . Location. buildSetting ( BuiltinMacros . HEADER_DEPENDENCIES)
326
350
327
- let message = " Missing entries in \( BuiltinMacros . HEADER_DEPENDENCIES. name) : \( missingDeps. map { $0. asBuildSettingEntryQuotedIfNeeded } . sorted ( ) . joined ( separator: " " ) ) "
351
+ missingDiagnostics = [ Diagnostic (
352
+ behavior: behavior,
353
+ location: location,
354
+ data: DiagnosticData ( message) ,
355
+ fixIts: fixIts) ]
356
+ }
357
+ else {
358
+ missingDiagnostics = [ ]
359
+ }
328
360
329
- let location : Diagnostic . Location = fixIt. map {
330
- Diagnostic . Location. path ( $0. sourceRange. path, line: $0. sourceRange. endLine, column: $0. sourceRange. endColumn)
331
- } ?? Diagnostic . Location. buildSetting ( BuiltinMacros . HEADER_DEPENDENCIES)
361
+ let unusedDiagnostics : [ Diagnostic ]
362
+ if !unusedDependencies. isEmpty {
363
+ let message = " Unused entries in \( BuiltinMacros . HEADER_DEPENDENCIES. name) : \( unusedDependencies. map { $0. name } . sorted ( ) . joined ( separator: " " ) ) "
364
+ // TODO location & fixit
365
+ unusedDiagnostics = [ Diagnostic (
366
+ behavior: validateUnused == . yesError ? . error : . warning,
367
+ location: . unknown,
368
+ data: DiagnosticData ( message) ,
369
+ fixIts: [ ] ) ]
370
+ }
371
+ else {
372
+ unusedDiagnostics = [ ]
373
+ }
332
374
333
- return [ Diagnostic (
334
- behavior: behavior,
335
- location: location,
336
- data: DiagnosticData ( message) ,
337
- fixIts: fixIts) ]
375
+ return missingDiagnostics + unusedDiagnostics
338
376
}
339
377
340
378
struct FixItContext : Sendable , SerializableCodable {
0 commit comments