-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
Copy pathcheck_imports.swift
executable file
·237 lines (219 loc) · 8.88 KB
/
check_imports.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
#!/usr/bin/swift
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Utility script for verifying `import` and `include` syntax. This ensures a
// consistent style as well as functionality across multiple package managers.
// For more context, see https://github.com/firebase/firebase-ios-sdk/blob/main/HeadersImports.md.
import Foundation
// Skip these directories. Imports should only be repo-relative in libraries
// and unit tests.
let skipDirPatterns = ["/Sample/", "/Pods/",
"FirebaseDynamicLinks/Tests/Integration",
"FirebaseInAppMessaging/Tests/Integration/",
"FirebaseAuth/",
// TODO: Turn Combine back on without Auth includes.
"FirebaseCombineSwift/Tests/Unit/FirebaseCombine-unit-Bridging-Header.h",
"SymbolCollisionTest/", "/gen/",
"IntegrationTesting/CocoapodsIntegrationTest/",
"FirebasePerformance/Tests/TestApp/",
"cmake-build-debug/", "build/", "ObjCIntegration/",
"FirebasePerformance/Tests/FIRPerfE2E/"] +
[
"CoreOnly/Sources", // Skip Firebase.h.
"SwiftPMTests", // The SwiftPM tests test module imports.
"IntegrationTesting/ClientApp", // The ClientApp tests module imports.
"FirebaseSessions/Protogen/", // Generated nanopb code with imports
] +
// The following are temporary skips pending working through a first pass of the repo:
[
"FirebaseDatabase/Sources/third_party/Wrap-leveldb", // Pending SwiftPM for leveldb.
"Example",
"Firestore",
"FirebasePerformance/ProtoSupport/",
]
// Skip existence test for patterns that start with the following:
let skipImportPatterns = [
"FBLPromise",
"OCMock",
"OCMStubRecorder",
]
private class ErrorLogger {
var foundError = false
func log(_ message: String) {
print(message)
foundError = true
}
func importLog(_ message: String, _ file: String, _ line: Int) {
log("Import Error: \(file):\(line) \(message)")
}
}
private func checkFile(_ file: String, logger: ErrorLogger, inRepo repoURL: URL,
isSwiftFile: Bool) {
var fileContents = ""
do {
fileContents = try String(contentsOfFile: file, encoding: .utf8)
} catch {
logger.log("Could not read \(file). \(error)")
// Not a source file, give up and return.
return
}
guard !isSwiftFile else {
// Swift specific checks.
fileContents.components(separatedBy: .newlines)
.enumerated() // [(lineNum, line), ...]
.filter { $1.starts(with: "import FirebaseCoreExtension") }
.forEach { lineNum, line in
logger
.importLog(
"Use `internal import FirebaseCoreExtension` when importing `FirebaseCoreExtension`.",
file, lineNum
)
}
return
}
let isPublic = file.range(of: "/Public/") != nil &&
// TODO: Skip legacy GDTCCTLibrary file that isn't Public and should be moved.
// This test is used in the GoogleDataTransport's repo's CI clone of this repo.
file.range(of: "GDTCCTLibrary/Public/GDTCOREvent+GDTCCTSupport.h") == nil
let isPrivate = file.range(of: "/Sources/Private/") != nil ||
// Delete when FirebaseInstallations fixes directory structure.
file.range(of: "Source/Library/Private/FirebaseInstallationsInternal.h") != nil ||
file.range(of: "FirebaseCore/Sources/FIROptionsInternal.h") != nil ||
file.range(of: "FirebaseCore/Extension") != nil
// Treat all files with names finishing on "Test" or "Tests" as files with tests.
let isTestFile = file.contains("Test.m") || file.contains("Tests.m") ||
file.contains("Test.swift") || file.contains("Tests.swift")
let isBridgingHeader = file.contains("Bridging-Header.h")
var inSwiftPackage = false
var inSwiftPackageElse = false
let lines = fileContents.components(separatedBy: .newlines)
var lineNum = 0
nextLine: for rawLine in lines {
let line = rawLine.trimmingCharacters(in: .whitespaces)
lineNum += 1
if line.starts(with: "#if SWIFT_PACKAGE") {
inSwiftPackage = true
} else if inSwiftPackage, line.starts(with: "#else") {
inSwiftPackage = false
inSwiftPackageElse = true
} else if inSwiftPackageElse, line.starts(with: "#endif") {
inSwiftPackageElse = false
} else if inSwiftPackage {
continue
} else if file.contains("FirebaseTestingSupport") {
// Module imports ok in SPM only test infrastructure.
continue
}
// "The #else of a SWIFT_PACKAGE check should only do CocoaPods module-style imports."
if line.starts(with: "#import") || line.starts(with: "#include") {
let importFile = line.components(separatedBy: " ")[1]
if inSwiftPackageElse {
if importFile.first != "<" {
logger
.importLog("Import in SWIFT_PACKAGE #else should start with \"<\".", file, lineNum)
}
continue
}
let importFileRaw = importFile.replacingOccurrences(of: "\"", with: "")
.replacingOccurrences(of: "<", with: "")
.replacingOccurrences(of: ">", with: "")
if importFile.first == "\"" {
// Public Headers should only use simple file names without paths.
if isPublic {
if importFile.contains("/") {
logger.importLog("Public header import should not include \"/\"", file, lineNum)
}
} else if !FileManager.default.fileExists(atPath: repoURL.path + "/" + importFileRaw) {
// Non-public header imports should be repo-relative paths. Unqualified imports are
// allowed in private headers.
if !isPrivate || importFile.contains("/") {
for skip in skipImportPatterns {
if importFileRaw.starts(with: skip) {
continue nextLine
}
}
logger.importLog("Import \(importFileRaw) does not exist.", file, lineNum)
}
}
} else if importFile.first == "<", !isPrivate, !isTestFile, !isBridgingHeader, !isPublic {
// Verify that double quotes are always used for intra-module imports.
if importFileRaw.starts(with: "Firebase"),
// Allow intra-module imports of FirebaseAppCheckInterop.
// TODO: Remove the FirebaseAppCheckInterop exception when it's moved to a separate repo.
importFile.range(of: "FirebaseAppCheckInterop/FirebaseAppCheckInterop.h") == nil {
logger
.importLog("Imports internal to the repo should use double quotes not \"<\"", file,
lineNum)
}
}
}
}
}
private func main() -> Int32 {
let logger = ErrorLogger()
// Search the path upwards to find the root of the firebase-ios-sdk repo.
var url = URL(fileURLWithPath: FileManager().currentDirectoryPath)
while url.path != "/" {
let script = url.appendingPathComponent("scripts/check_imports.swift")
if FileManager.default.fileExists(atPath: script.path) {
break
}
url = url.deletingLastPathComponent()
}
let repoURL = url
guard let contents = try? FileManager.default.contentsOfDirectory(at: repoURL,
includingPropertiesForKeys: nil,
options: [.skipsHiddenFiles])
else {
logger.log("Failed to get repo contents \(repoURL)")
return 1
}
for rootURL in contents {
if !rootURL.hasDirectoryPath {
continue
}
let enumerator = FileManager.default.enumerator(atPath: rootURL.path)
whileLoop: while let file = enumerator?.nextObject() as? String {
if let fType = enumerator?.fileAttributes?[FileAttributeKey.type] as? FileAttributeType,
fType == .typeRegular {
if file.starts(with: ".") {
continue
}
if !(file.hasSuffix(".h") ||
file.hasSuffix(".m") ||
file.hasSuffix(".mm") ||
file.hasSuffix(".c") ||
file.hasSuffix(".swift")) {
continue
}
let fullTransformPath = rootURL.path + "/" + file
for dirPattern in skipDirPatterns {
if fullTransformPath.range(of: dirPattern) != nil {
continue whileLoop
}
}
checkFile(
fullTransformPath,
logger: logger,
inRepo: repoURL,
isSwiftFile: file.hasSuffix(".swift")
)
}
}
}
return logger.foundError ? 1 : 0
}
exit(main())