diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000000..1b6575e797a --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Ignore Swift Package Manager build artifacts directory. +.build/ +# Ignore generated Xcode projects +*.xcodeproj + +# We always build swiftSyntax of master dependencies. Ignore any fixed +# dependency versions. +Package.resolved + +.DS_Store \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000000..61b0c78195f --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,211 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + + + +## Runtime Library Exception to the Apache 2.0 License: ## + + + As an exception, if you use this Software to compile your source code and + portions of this Software are embedded into the binary product as a result, + you may redistribute such product without providing attribution as would + otherwise be required by Sections 4(a), 4(b) and 4(d) of the License. diff --git a/Package.swift b/Package.swift new file mode 100644 index 00000000000..ab0fab27c31 --- /dev/null +++ b/Package.swift @@ -0,0 +1,18 @@ +// swift-tools-version:4.2 + +import PackageDescription + +let package = Package( + name: "SwiftSyntax", + products: [ + .library(name: "SwiftSyntax", type: .dynamic, targets: ["SwiftSyntax"]), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-package-manager.git", .branch("master")) + ], + targets: [ + .target(name: "SwiftSyntax", dependencies: ["Utility"]), + .testTarget(name: "SwiftSyntaxTest", dependencies: ["SwiftSyntax"], exclude: ["Inputs"]), + .target(name: "lit-test-helper", dependencies: ["SwiftSyntax"]) + ] +) diff --git a/Sources/SwiftSyntax/.gitignore b/Sources/SwiftSyntax/.gitignore new file mode 100644 index 00000000000..6d6d11946b6 --- /dev/null +++ b/Sources/SwiftSyntax/.gitignore @@ -0,0 +1 @@ +gyb_generated \ No newline at end of file diff --git a/Sources/SwiftSyntax/CMakeLists.txt b/Sources/SwiftSyntax/CMakeLists.txt deleted file mode 100644 index 6e0f94395bb..00000000000 --- a/Sources/SwiftSyntax/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ -add_swift_library(swiftSwiftSyntax SHARED - # This file should be listed the first. Module name is inferred from the - # filename. - SwiftSyntax.swift - AbsolutePosition.swift - AtomicCache.swift - ByteTreeDeserialization.swift - Diagnostic.swift - DiagnosticConsumer.swift - DiagnosticEngine.swift - JSONDiagnosticConsumer.swift - PrintingDiagnosticConsumer.swift - RawSyntax.swift - SourcePresence.swift - SourceLength.swift - SwiftcInvocation.swift - Syntax.swift - SyntaxData.swift - SyntaxChildren.swift - SyntaxCollections.swift.gyb - SyntaxClassifier.swift.gyb - SyntaxBuilders.swift.gyb - SyntaxFactory.swift.gyb - SyntaxKind.swift.gyb - SyntaxNodes.swift.gyb - SyntaxRewriter.swift.gyb - TokenKind.swift.gyb - Trivia.swift.gyb - - SWIFT_MODULE_DEPENDS Foundation - INSTALL_IN_COMPONENT swift-syntax - TARGET_SDKS OSX - IS_STDLIB) diff --git a/Sources/SwiftSyntax/SwiftSyntax.swift b/Sources/SwiftSyntax/SwiftSyntax.swift index 0c0be778e39..9a3a5e1a719 100644 --- a/Sources/SwiftSyntax/SwiftSyntax.swift +++ b/Sources/SwiftSyntax/SwiftSyntax.swift @@ -1,4 +1,4 @@ -//===--------------- SwiftLanguage.swift - Swift Syntax Library -----------===// +//===--------------- SwiftSyntax.swift - Swift Syntax Library -------------===// // // This source file is part of the Swift.org open source project // @@ -120,6 +120,16 @@ public final class SyntaxTreeDeserializer { } } +fileprivate extension Array { + func withUnsafeData(_ body: (Data) throws -> T) rethrows -> T { + return try self.withUnsafeBufferPointer { + (pointer: UnsafeBufferPointer) -> T in + let data = Data(buffer: pointer) + return try body(data) + } + } +} + /// Namespace for functions to retrieve a syntax tree from the swift compiler /// and deserializing it. public enum SyntaxTreeParser { @@ -133,11 +143,13 @@ public enum SyntaxTreeParser { public static func parse(_ url: URL) throws -> SourceFileSyntax { let swiftcRunner = try SwiftcRunner(sourceFile: url) let result = try swiftcRunner.invoke() - let syntaxTreeData = result.stdoutData - let deserializer = SyntaxTreeDeserializer() - // FIXME: We should use ByteTree as the internal transfer format - return try deserializer.deserialize(syntaxTreeData, - serializationFormat: .json) + return try result.output.dematerialize().withUnsafeData { + (syntaxTreeData: Data) -> SourceFileSyntax in + let deserializer = SyntaxTreeDeserializer() + // FIXME: We should use ByteTree as the internal transfer format + return try deserializer.deserialize(syntaxTreeData, + serializationFormat: .json) + } } } diff --git a/Sources/SwiftSyntax/SwiftcInvocation.swift b/Sources/SwiftSyntax/SwiftcInvocation.swift index 70dc55a7d2f..43bbac74ee2 100644 --- a/Sources/SwiftSyntax/SwiftcInvocation.swift +++ b/Sources/SwiftSyntax/SwiftcInvocation.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import Foundation +import Basic #if os(macOS) import Darwin @@ -20,102 +21,22 @@ import Darwin import Glibc #endif -/// The result of process execution, containing the exit status code, -/// stdout, and stderr -struct ProcessResult { - /// The process exit code. A non-zero exit code usually indicates failure. - let exitCode: Int - - /// The contents of the process's stdout as Data. - let stdoutData: Data - - /// The contents of the process's stderr as Data. - let stderrData: Data - - /// The contents of the process's stdout, assuming the data was UTF-8 encoded. - var stdout: String { - return String(data: stdoutData, encoding: .utf8)! - } - - /// The contents of the process's stderr, assuming the data was UTF-8 encoded. - var stderr: String { - return String(data: stderrData, encoding: .utf8)! - } - - /// Whether or not this process had a non-zero exit code. - var wasSuccessful: Bool { - return exitCode == 0 - } -} - -internal func runCore(_ executable: URL, _ arguments: [String] = []) - -> ProcessResult { - let stdoutPipe = Pipe() - var stdoutData = Data() - stdoutPipe.fileHandleForReading.readabilityHandler = { file in - stdoutData.append(file.availableData) - } - - let stderrPipe = Pipe() - var stderrData = Data() - stderrPipe.fileHandleForReading.readabilityHandler = { file in - stderrData.append(file.availableData) - } - - let process = Process() - process.terminationHandler = { process in - stdoutPipe.fileHandleForReading.readabilityHandler = nil - stderrPipe.fileHandleForReading.readabilityHandler = nil - } - process.launchPath = executable.path - process.arguments = arguments - process.standardOutput = stdoutPipe - process.standardError = stderrPipe - process.launch() - process.waitUntilExit() - - return ProcessResult(exitCode: Int(process.terminationStatus), - stdoutData: stdoutData, - stderrData: stderrData) -} - /// Runs the provided executable with the provided arguments and returns the /// contents of stdout and stderr as Data. /// - Parameters: -/// - executable: The full file URL to the executable you're running. +/// - executable: The full file path to the executable you're running. /// - arguments: A list of strings to pass to the process as arguments. /// - Returns: A ProcessResult containing stdout, stderr, and the exit code. -func run(_ executable: URL, arguments: [String] = []) -> ProcessResult { +func run(_ executable: AbsolutePath, arguments: [String] = []) throws + -> ProcessResult { #if _runtime(_ObjC) // Use an autoreleasepool to prevent memory- and file-descriptor leaks. - return autoreleasepool { () -> ProcessResult in - runCore(executable, arguments) + return try autoreleasepool { () -> ProcessResult in + return try Basic.Process.popen(arguments: [executable.asString] + arguments) } #else - return runCore(executable, arguments) -#endif -} - -/// Finds the dylib or executable which the provided address falls in. -/// - Parameter dsohandle: A pointer to a symbol in the object file you're -/// looking for. If not provided, defaults to the -/// caller's `#dsohandle`, which will give you the -/// object file the caller resides in. -/// - Returns: A File URL pointing to the object where the provided address -/// resides. This may be a dylib, shared object, static library, -/// or executable. If unable to find the appropriate object, returns -/// `nil`. -func findFirstObjectFile(for dsohandle: UnsafeRawPointer = #dsohandle) -> URL? { -#if os(Linux) - var info = Dl_info() -#else - var info = dl_info() + return try Basic.Process.popen(arguments: [executable.asString] + arguments) #endif - if dladdr(dsohandle, &info) == 0 { - return nil - } - let path = String(cString: info.dli_fname) - return URL(fileURLWithPath: path) } enum InvocationError: Error, CustomStringConvertible { @@ -133,75 +54,44 @@ enum InvocationError: Error, CustomStringConvertible { } struct SwiftcRunner { - /// Gets the `swiftc` binary packaged alongside this library. - /// - Returns: The path to `swiftc` relative to the path of this library - /// file, or `nil` if it could not be found. - /// - Note: This makes assumptions about your Swift installation directory - /// structure. Importantly, it assumes that the directory tree is - /// shaped like this: - /// ``` - /// install_root/ - /// - bin/ - /// - swiftc - /// - lib/ - /// - swift/ - /// - ${target}/ - /// - ${arch}/ (only on !Darwin and if launched from Xcode) - /// - libswiftSwiftSyntax.[dylib|so] - /// ``` - static func locateSwiftc() -> URL? { - guard let libraryPath = findFirstObjectFile() else { return nil } - - var swiftcURL = libraryPath.deletingLastPathComponent() - .deletingLastPathComponent() - .deletingLastPathComponent() - .deletingLastPathComponent() - - if swiftcURL.lastPathComponent == "lib" { - // We are still one level to deep because we started in ${arch}, - // not ${target}, see comment above). Traverse one more level upwards - swiftcURL = swiftcURL.deletingLastPathComponent() - } - - swiftcURL = swiftcURL.appendingPathComponent("bin") - .appendingPathComponent("swiftc") - guard FileManager.default.fileExists(atPath: swiftcURL.path) else { - return nil - } - return swiftcURL - } - #if os(macOS) /// The location of the macOS SDK, or `nil` if it could not be found. static let macOSSDK: String? = { - let url = URL(fileURLWithPath: "/usr/bin/env") - let result = run(url, arguments: ["xcrun", "--show-sdk-path"]) - guard result.wasSuccessful else { return nil } - let toolPath = result.stdout.trimmingCharacters(in: .whitespacesAndNewlines) - if toolPath.isEmpty { return nil } - return toolPath + do { + guard let xcrunPath = Basic.Process.findExecutable("xcrun") else { + return nil + } + let result = try run(xcrunPath, arguments: ["--show-sdk-path"]) + guard result.exitStatus == .terminated(code: 0) else { return nil } + let output = try result.utf8Output() + let toolPath = output.trimmingCharacters(in: .whitespacesAndNewlines) + if toolPath.isEmpty { return nil } + return toolPath + } catch { + return nil + } }() #endif - - /// Internal static cache of the Swiftc path. - static let _swiftcURL: URL? = SwiftcRunner.locateSwiftc() - - /// The URL where the `swiftc` binary lies. - let swiftcURL: URL + /// The URL where the `swiftc` binary lies. If `nil` the `swiftc` binary is + // looked up from the `PATH` environment variable. + let swiftcURL: URL? /// The source file being parsed. let sourceFile: URL - /// Creates a SwiftcRunner that will parse and emit the syntax - /// tree for a provided source file. /// - Parameter sourceFile: The URL to the source file you're trying /// to parse. - init(sourceFile: URL) throws { - guard let url = SwiftcRunner._swiftcURL else { - throw InvocationError.couldNotFindSwiftc - } - self.swiftcURL = url + + /// Creates a SwiftcRunner that will parse and emit the syntax + /// tree for a provided source file. + /// - Parameters: + /// - sourceFile: The URL to the source file you're trying to parse + /// - swiftcURL: The URL where the `swiftc` binary lies. If `nil` the + /// `swiftc` binary is looked up from the `PATH` environment + /// variable. + init(sourceFile: URL, swiftcURL: URL? = nil) throws { self.sourceFile = sourceFile + self.swiftcURL = swiftcURL } /// Invokes swiftc with the provided arguments. @@ -215,6 +105,15 @@ struct SwiftcRunner { arguments.append("-sdk") arguments.append(sdk) #endif - return run(swiftcURL, arguments: arguments) + let swiftcPath: AbsolutePath + if let swiftcURL = self.swiftcURL { + swiftcPath = AbsolutePath(swiftcURL.path) + } else { + guard let lookupResult = Basic.Process.findExecutable("swiftc") else { + throw InvocationError.couldNotFindSwiftc + } + swiftcPath = lookupResult + } + return try run(swiftcPath, arguments: arguments) } } diff --git a/Sources/SwiftSyntax/SyntaxBuilders.swift.gyb b/Sources/SwiftSyntax/SyntaxBuilders.swift.gyb index 0016fcd9ec4..9a6d40f2d03 100644 --- a/Sources/SwiftSyntax/SyntaxBuilders.swift.gyb +++ b/Sources/SwiftSyntax/SyntaxBuilders.swift.gyb @@ -1,4 +1,3 @@ - %{ # -*- mode: Swift -*- from gyb_syntax_support import * diff --git a/Sources/lit-test-helper/ClassifiedSyntaxTreePrinter.swift b/Sources/lit-test-helper/ClassifiedSyntaxTreePrinter.swift new file mode 100644 index 00000000000..9fa3bdb0c07 --- /dev/null +++ b/Sources/lit-test-helper/ClassifiedSyntaxTreePrinter.swift @@ -0,0 +1,113 @@ +import SwiftSyntax +import Foundation + +extension SyntaxClassification { + var tag: String { + switch self { + case .none: return "" + case .keyword: return "kw" + case .identifier: return "" + case .typeIdentifier: return "type" + case .dollarIdentifier: return "dollar" + case .integerLiteral: return "int" + case .floatingLiteral: return "float" + case .stringLiteral: return "str" + case .stringInterpolationAnchor: return "anchor" + case .poundDirectiveKeyword: return "#kw" + case .buildConfigId: return "#id" + case .attribute: return "attr-builtin" + case .objectLiteral: return "object-literal" + case .editorPlaceholder: return "placeholder" + case .lineComment: return "comment-line" + case .blockComment: return "comment-block" + case .docLineComment: return "doc-comment-line" + case .docBlockComment: return "doc-comment-block" + } + } +} + +class ClassifiedSyntaxTreePrinter: SyntaxVisitor { + private let classifications: [TokenSyntax: SyntaxClassification] + private var currentClassification = SyntaxClassification.none + private var skipNextNewline = false + private var result = "" + + // MARK: Public interface + + init(classifications: [TokenSyntax: SyntaxClassification]) { + self.classifications = classifications + } + + func print(tree: SourceFileSyntax) -> String { + result = "" + visit(tree) + // Emit the last closing tag + recordCurrentClassification(.none) + return result + } + + // MARK: Implementation + + /// Closes the current tag if it is different from the previous one and opens + /// a tag with the specified ID. + private func recordCurrentClassification( + _ classification: SyntaxClassification + ) { + if currentClassification != classification { + if currentClassification != .none { + result += "" + } + if classification != .none { + result += "<" + classification.tag + ">" + } + } + currentClassification = classification + } + + private func visit(_ piece: TriviaPiece) { + let classification: SyntaxClassification + switch piece { + case .spaces, .tabs, .verticalTabs, .formfeeds: + classification = .none + case .newlines, .carriageReturns, .carriageReturnLineFeeds: + if skipNextNewline { + skipNextNewline = false + return + } + classification = .none + case .backticks: + classification = .none + case .lineComment(let text): + // Don't print CHECK lines + if text.hasPrefix("// CHECK") { + skipNextNewline = true + return + } + classification = .lineComment + case .blockComment: + classification = .blockComment + case .docLineComment: + classification = .docLineComment + case .docBlockComment: + classification = .docBlockComment + case .garbageText: + classification = .none + } + recordCurrentClassification(classification) + piece.write(to: &result) + } + + private func visit(_ trivia: Trivia) { + for piece in trivia { + visit(piece) + } + } + + override func visit(_ node: TokenSyntax) { + visit(node.leadingTrivia) + let classification = classifications[node] ?? SyntaxClassification.none + recordCurrentClassification(classification) + result += node.text + visit(node.trailingTrivia) + } +} diff --git a/Sources/lit-test-helper/main.swift b/Sources/lit-test-helper/main.swift new file mode 100644 index 00000000000..ff40c5051a5 --- /dev/null +++ b/Sources/lit-test-helper/main.swift @@ -0,0 +1,217 @@ +import SwiftSyntax +import Foundation +import Utility +import Basic + +/// Print the given message to stderr +func printerr(_ message: String, terminator: String = "\n") { + FileHandle.standardError.write((message + terminator).data(using: .utf8)!) +} + +extension SerializationFormat: ArgumentKind { + public init(argument: String) throws { + switch argument { + case "json": + self = .json + case "byteTree": + self = .byteTree + default: + throw ArgumentConversionError.unknown(value: argument) + } + } + + public static var completion: ShellCompletion { + return .values([ + ("json", "Serialize the syntax tree as JSON"), + ("byteTree", "Serialize the syntax tree as ByteTree") + ]) + } +} + +extension Foundation.URL: ArgumentKind { + public init(argument: String) throws { + self.init(fileURLWithPath: argument) + } + + public static var completion: ShellCompletion { + return .filename + } +} + +extension ArgumentParser.Result { + func getOrThrow(_ argument: OptionArgument, name: String) throws -> T { + if let value = self.get(argument) { + return value + } else { + throw ArgumentParserError.expectedValue(option: name) + } + } +} + +protocol Subcommand { + var command: String { get } + var overview: String { get } + + init(parser: ArgumentParser) + func run(with: ArgumentParser.Result) throws +} + +struct DeserializeCommand: Subcommand { + var command = "deserialize" + var overview = "Deserialize a full pre-edit syntax tree (-pre-edit-tree) " + + "and write the source representation of the syntax tree to an out file " + + "(-out)." + + let preEditTree: OptionArgument + let outFile: OptionArgument + let format: OptionArgument + + init(parser: ArgumentParser) { + let subparser = parser.add(subparser: command, overview: overview) + + preEditTree = subparser.add(option: "-pre-edit-tree", kind: URL.self, + usage: "The path to the serialized syntax " + + "tree that shall be deserialized.") + + outFile = subparser.add(option: "-out", kind: URL.self, usage: + "The file to which the source representation of the deserialized " + + "syntax tree shall be written.") + + format = subparser.add(option: "-serialization-format", + kind: SerializationFormat.self, + usage: "The format that shall be used to " + + "serialize/deserialize the syntax tree. " + + "Defaults to json.") + } + + func run(with args: ArgumentParser.Result) throws { + let fileURL = try args.getOrThrow(preEditTree, name: "-pre-edit-tree") + let outURL = try args.getOrThrow(outFile, name: "-out") + let format = args.get(self.format) ?? .json + + let fileData = try Data(contentsOf: fileURL) + + let deserializer = SyntaxTreeDeserializer() + let tree = try deserializer.deserialize(fileData, + serializationFormat: format) + + let sourceRepresenation = tree.description + try sourceRepresenation.write(to: outURL, atomically: false, + encoding: .utf8) + } +} + +struct DeserializeIncrementalCommand: Subcommand { + var command = "deserialize-incremental" + var overview = "Deserialize a full pre-edit syntax tree (-pre-edit-tree), " + + "parse an incrementally transferred post-edit syntax tree (-incr-tree) " + + "and write the source representation of the post-edit syntax tree to an " + + "out file (-out)." + + let preEditTree: OptionArgument + let incrTree: OptionArgument + let outFile: OptionArgument + let format: OptionArgument + + init(parser: ArgumentParser) { + let subparser = parser.add(subparser: command, overview: overview) + + preEditTree = subparser.add(option: "-pre-edit-tree", kind: URL.self, + usage: "The path to a serialized pre-edit" + + "syntax tree.") + + incrTree = subparser.add(option: "-incr-tree", kind: URL.self, + usage: "The path to a serialized incrementally " + + "transferred post-edit syntax tree") + + outFile = subparser.add(option: "-out", kind: URL.self, usage: + "The file to which the source representation of the post-edit syntax " + + "tree shall be written.") + + format = subparser.add(option: "-serialization-format", + kind: SerializationFormat.self, + usage: "The format that shall be used to " + + "serialize/deserialize the syntax tree. " + + "Defaults to json.") + } + + func run(with args: ArgumentParser.Result) throws { + let preEditTreeURL = try args.getOrThrow(preEditTree, + name: "-pre-edit-tree") + let incrTreeURL = try args.getOrThrow(incrTree, name: "-incr-tree") + let outURL = try args.getOrThrow(outFile, name: "-out") + let format = args.get(self.format) ?? .json + + let preEditTreeData = try Data(contentsOf: preEditTreeURL) + let incrTreeData = try Data(contentsOf: incrTreeURL) + + let deserializer = SyntaxTreeDeserializer() + _ = try deserializer.deserialize(preEditTreeData, + serializationFormat: format) + let tree = try deserializer.deserialize(incrTreeData, + serializationFormat: format) + let sourceRepresenation = tree.description + try sourceRepresenation.write(to: outURL, atomically: false, + encoding: .utf8) + } +} + +struct ClassifySyntaxCommand: Subcommand { + var command = "classify-syntax" + var overview = "Parse the given source file (-source-file) and output it " + + "with tokens classified for syntax colouring." + + let sourceFile: OptionArgument + let outFile: OptionArgument + + init(parser: ArgumentParser) { + let subparser = parser.add(subparser: command, overview: overview) + + sourceFile = subparser.add(option: "-source-file", kind: URL.self, usage: + "The path to a Swift source file to classify") + + outFile = subparser.add(option: "-out", kind: URL.self, usage: + "The file to which the classified source file shall be written. If " + + "omitted the classified source file will be written to stdout.") + } + + func run(with args: ArgumentParser.Result) throws { + let treeURL = try args.getOrThrow(sourceFile, name: "-source-file") + + let tree = try SyntaxTreeParser.parse(treeURL) + let classifications = SyntaxClassifier.classifyTokensInTree(tree) + let printer = ClassifiedSyntaxTreePrinter(classifications: classifications) + let result = printer.print(tree: tree) + + if let outURL = args.get(outFile) { + try result.write(to: outURL, atomically: false, encoding: .utf8) + } else { + print(result) + } + } +} + +do { + let parser = ArgumentParser(usage: " ",overview: + "Utility to test SwiftSyntax syntax tree deserialization.") + + let subcommands: [Subcommand] = [ + DeserializeCommand(parser: parser), + DeserializeIncrementalCommand(parser: parser), + ClassifySyntaxCommand(parser: parser) + ] + + let result = try parser.parse(Array(CommandLine.arguments.dropFirst())) + + guard let subparser = result.subparser(parser), + let command = subcommands.first(where: { $0.command == subparser }) else { + parser.printUsage(on: stdoutStream) + exit(1) + } + try command.run(with: result) + exit(0) +} catch { + printerr("\(error)") + printerr("Run lit-test-helper --help for more help.") + exit(1) +} diff --git a/Tests/SwiftSyntaxTest/AbsolutePosition.swift b/Tests/SwiftSyntaxTest/AbsolutePosition.swift new file mode 100644 index 00000000000..b09e1d8c417 --- /dev/null +++ b/Tests/SwiftSyntaxTest/AbsolutePosition.swift @@ -0,0 +1,129 @@ +import XCTest +import SwiftSyntax + +fileprivate class FuncRenamer: SyntaxRewriter { + override func visit(_ node: FunctionDeclSyntax) ->DeclSyntax { + return (super.visit(node) as! FunctionDeclSyntax).withIdentifier( + SyntaxFactory.makeIdentifier("anotherName")) + } +} + +public class AbsolutePositionTestCase: XCTestCase { + public func testVisitor() { + XCTAssertNoThrow(try { + let source = try String(contentsOf: getInput("visitor.swift")) + let parsed = try SyntaxTreeParser.parse(getInput("visitor.swift")) + XCTAssertEqual(0, parsed.position.utf8Offset) + XCTAssertEqual(source.count, + parsed.eofToken.positionAfterSkippingLeadingTrivia.utf8Offset) + XCTAssertEqual(0, parsed.position.utf8Offset) + XCTAssertEqual(source.count, parsed.byteSize) + }()) + } + + public func testClosure() { + XCTAssertNoThrow(try { + let source = try String(contentsOf: getInput("closure.swift")) + let parsed = try SyntaxTreeParser.parse(getInput("closure.swift")) + XCTAssertEqual(source.count, + parsed.eofToken.positionAfterSkippingLeadingTrivia.utf8Offset) + XCTAssertEqual(0, parsed.position.utf8Offset) + XCTAssertEqual(source.count, parsed.byteSize) + }()) + } + + public func testRename() { + XCTAssertNoThrow(try { + let parsed = try SyntaxTreeParser.parse(getInput("visitor.swift")) + let renamed = FuncRenamer().visit(parsed) as! SourceFileSyntax + let renamedSource = renamed.description + XCTAssertEqual(renamedSource.count, + renamed.eofToken.positionAfterSkippingLeadingTrivia.utf8Offset) + XCTAssertEqual(renamedSource.count, renamed.byteSize) + }()) + } + + public func testCurrentFile() { + XCTAssertNoThrow(try { + let parsed = try SyntaxTreeParser.parse(URL(fileURLWithPath: #file)) + class Visitor: SyntaxVisitor { + override func visitPre(_ node: Syntax) { + _ = node.position + _ = node.byteSize + _ = node.positionAfterSkippingLeadingTrivia + } + override func visit(_ node: TokenSyntax) { + XCTAssertEqual(node.positionAfterSkippingLeadingTrivia.utf8Offset, + node.position.utf8Offset + node.leadingTrivia.byteSize) + } + } + Visitor().visit(parsed) + }()) + } + + public func testRecursion() { + var l = [CodeBlockItemSyntax]() + let idx = 2000 + for _ in 0...idx { + l.append(SyntaxFactory.makeCodeBlockItem( + item: SyntaxFactory.makeReturnStmt( + returnKeyword: SyntaxFactory.makeToken(.returnKeyword, presence: .present) + .withTrailingTrivia(.newlines(1)), expression: nil), semicolon: nil, errorTokens: nil)) + } + let root = SyntaxFactory.makeSourceFile( + statements: SyntaxFactory.makeCodeBlockItemList(l), + eofToken: SyntaxFactory.makeToken(.eof, presence: .present)) + _ = root.statements[idx].position + _ = root.statements[idx].byteSize + _ = root.statements[idx].positionAfterSkippingLeadingTrivia + } + + func createSourceFile(_ count: Int) -> SourceFileSyntax { + let leading = Trivia(pieces: [ + .newlines(1), + .backticks(1), + .docLineComment("/// some comment") + ]) + let trailing = Trivia.docLineComment("/// This is comment\n") + let items : [CodeBlockItemSyntax] = + [CodeBlockItemSyntax](repeating: CodeBlockItemSyntax { + $0.useItem(ReturnStmtSyntax { + $0.useReturnKeyword( + SyntaxFactory.makeReturnKeyword( + leadingTrivia: leading, + trailingTrivia: trailing)) + })}, count: count) + return SyntaxFactory.makeSourceFile( + statements: SyntaxFactory.makeCodeBlockItemList(items), + eofToken: SyntaxFactory.makeToken(.eof, presence: .present)) + } + + public func testTrivias() { + let idx = 5 + let root = self.createSourceFile(idx + 1) + XCTAssertEqual(3, root.leadingTrivia!.count) + XCTAssertEqual(0, root.trailingTrivia!.count) + let state = root.statements[idx] + XCTAssertEqual(3, state.leadingTrivia!.count) + XCTAssertEqual(1, state.trailingTrivia!.count) + XCTAssertEqual(state.byteSize, + state.leadingTrivia!.byteSize + state.trailingTrivia!.byteSize + + state.byteSizeAfterTrimmingTrivia) + XCTAssertFalse(root.statements.isImplicit) + } + + public func testImplicit() { + let root = self.createSourceFile(0) + XCTAssertTrue(root.statements.isImplicit) + } + + public func testWithoutSourceFileRoot() { + let item = SyntaxFactory.makeCodeBlockItem( + item: SyntaxFactory.makeReturnStmt( + returnKeyword: SyntaxFactory.makeToken(.returnKeyword, presence: .present) + .withLeadingTrivia(.newlines(1)).withTrailingTrivia(.newlines(1)), + expression: nil), semicolon: nil, errorTokens: nil) + XCTAssertEqual(0, item.position.utf8Offset) + XCTAssertEqual(1, item.positionAfterSkippingLeadingTrivia.utf8Offset) + } +} diff --git a/Tests/SwiftSyntaxTest/DeserializeFile.swift b/Tests/SwiftSyntaxTest/DeserializeFile.swift new file mode 100644 index 00000000000..b0855e1ee5d --- /dev/null +++ b/Tests/SwiftSyntaxTest/DeserializeFile.swift @@ -0,0 +1,13 @@ +import XCTest +import SwiftSyntax + +public class DecodeSytnaxTestCase: XCTestCase { + public func testBasic() { + XCTAssertNoThrow(try { + let inputFile = getInput("visitor.swift") + let source = try String(contentsOf: inputFile) + let parsed = try SyntaxTreeParser.parse(inputFile) + XCTAssertEqual("\(parsed)", source) + }()) + } +} diff --git a/Tests/SwiftSyntaxTest/DiagnosticTest.swift b/Tests/SwiftSyntaxTest/DiagnosticTest.swift new file mode 100644 index 00000000000..329c1b468bc --- /dev/null +++ b/Tests/SwiftSyntaxTest/DiagnosticTest.swift @@ -0,0 +1,94 @@ +import XCTest +import SwiftSyntax + +fileprivate func loc(_ file: String = #file, line: Int = #line, + column: Int = #line) -> SourceLocation { + return SourceLocation(line: line, column: column, offset: 0, file: file) +} + +/// Adds static constants to Diagnostic.Message. +fileprivate extension Diagnostic.Message { + /// Error thrown when a conversion between two types is impossible. + static func cannotConvert(fromType: String, + toType: String) -> Diagnostic.Message { + return .init(.error, + "cannot convert value of type '\(fromType)' to '\(toType)'") + } + + /// Suggestion for the user to explicitly check a value does not equal zero. + static let checkEqualToZero = + Diagnostic.Message(.note, "check for explicit equality to '0'") + + static func badFunction(_ name: TokenSyntax) -> Diagnostic.Message { + return .init(.error, "bad function '\(name.text)'") + } + static func endOfFunction(_ name: TokenSyntax) -> Diagnostic.Message { + return .init(.warning, "end of function '\(name.text)'") + } +} + +public class DiagnosticTestCase: XCTestCase { + public func testDiagnosticEmission() { + let startLoc = loc() + let fixLoc = loc() + + let engine = DiagnosticEngine() + XCTAssertFalse(engine.hasErrors) + + engine.diagnose(.cannotConvert(fromType: "Int", toType: "Bool"), + location: startLoc) { + $0.note(.checkEqualToZero, location: fixLoc, + fixIts: [.insert(fixLoc, " != 0")]) + } + + XCTAssertEqual(engine.diagnostics.count, 1) + guard let diag = engine.diagnostics.first else { return } + XCTAssertEqual(diag.message.text, + "cannot convert value of type 'Int' to 'Bool'") + XCTAssertEqual(diag.message.severity, .error) + XCTAssertEqual(diag.notes.count, 1) + XCTAssertTrue(engine.hasErrors) + + guard let note = diag.notes.first else { return } + XCTAssertEqual(note.message.text, "check for explicit equality to '0'") + XCTAssertEqual(note.message.severity, .note) + XCTAssertEqual(note.fixIts.count, 1) + + guard let fixIt = note.fixIts.first else { return } + XCTAssertEqual(fixIt.text, " != 0") + } + + public func testSourceLocations() { + let engine = DiagnosticEngine() + // engine.addConsumer(PrintingDiagnosticConsumer()) + let url = getInput("diagnostics.swift") + + class Visitor: SyntaxVisitor { + let url: URL + let engine: DiagnosticEngine + init(url: URL, engine: DiagnosticEngine) { + self.url = url + self.engine = engine + } + override func visit(_ function: FunctionDeclSyntax) { + let startLoc = function.identifier.startLocation(in: url) + let endLoc = function.endLocation(in: url) + engine.diagnose(.badFunction(function.identifier), location: startLoc) { + $0.highlight(function.identifier.sourceRange(in: self.url)) + } + engine.diagnose(.endOfFunction(function.identifier), location: endLoc) + } + } + + XCTAssertNoThrow(try { + let file = try SyntaxTreeParser.parse(url) + Visitor(url: url, engine: engine).visit(file) + }()) + + XCTAssertEqual(6, engine.diagnostics.count) + let lines = Set(engine.diagnostics.compactMap { $0.location?.line }) + XCTAssertEqual([1, 3, 5, 7, 9, 11], lines) + let columns = Set(engine.diagnostics.compactMap { $0.location?.column }) + XCTAssertEqual([6, 2], columns) + } +} diff --git a/Tests/SwiftSyntaxTest/Inputs/closure.swift b/Tests/SwiftSyntaxTest/Inputs/closure.swift new file mode 100644 index 00000000000..64b2c2aa20b --- /dev/null +++ b/Tests/SwiftSyntaxTest/Inputs/closure.swift @@ -0,0 +1,3 @@ +// A closure without a signature. The test will ensure it stays the same after +// applying a rewriting pass. +let x: () -> Void = {} \ No newline at end of file diff --git a/Tests/SwiftSyntaxTest/Inputs/diagnostics.swift b/Tests/SwiftSyntaxTest/Inputs/diagnostics.swift new file mode 100644 index 00000000000..e9a6f8ce4d9 --- /dev/null +++ b/Tests/SwiftSyntaxTest/Inputs/diagnostics.swift @@ -0,0 +1,11 @@ +func foo() { + +} + +func bar() { + +} + +func baz() { + +} \ No newline at end of file diff --git a/Tests/SwiftSyntaxTest/Inputs/near-empty.swift b/Tests/SwiftSyntaxTest/Inputs/near-empty.swift new file mode 100644 index 00000000000..8ba3a16384a --- /dev/null +++ b/Tests/SwiftSyntaxTest/Inputs/near-empty.swift @@ -0,0 +1 @@ +n diff --git a/Tests/SwiftSyntaxTest/Inputs/nested-blocks.swift b/Tests/SwiftSyntaxTest/Inputs/nested-blocks.swift new file mode 100644 index 00000000000..9993ae44877 --- /dev/null +++ b/Tests/SwiftSyntaxTest/Inputs/nested-blocks.swift @@ -0,0 +1,8 @@ +struct Foo { + func foo() { + print("hello") + func bar() { + print("goodbye") + } + } +} diff --git a/Tests/SwiftSyntaxTest/Inputs/visitor.swift b/Tests/SwiftSyntaxTest/Inputs/visitor.swift new file mode 100644 index 00000000000..3597e470aa8 --- /dev/null +++ b/Tests/SwiftSyntaxTest/Inputs/visitor.swift @@ -0,0 +1,7 @@ +func foo() { + func foo() { + func foo() { + /*Unknown token */0xG + } + } +} diff --git a/Tests/SwiftSyntaxTest/LazyCaching.swift b/Tests/SwiftSyntaxTest/LazyCaching.swift new file mode 100644 index 00000000000..08960199031 --- /dev/null +++ b/Tests/SwiftSyntaxTest/LazyCaching.swift @@ -0,0 +1,40 @@ +import XCTest +import SwiftSyntax + +class LazyCachingTestCase: XCTestCase { + public func testPathological() { + let tuple = SyntaxFactory.makeVoidTupleType() + + DispatchQueue.concurrentPerform(iterations: 100) { _ in + XCTAssertEqual(tuple.leftParen, tuple.leftParen) + } + } + + public func testTwoAccesses() { + let tuple = SyntaxFactory.makeVoidTupleType() + + let queue1 = DispatchQueue(label: "queue1") + let queue2 = DispatchQueue(label: "queue2") + + var node1: TokenSyntax? + var node2: TokenSyntax? + + let group = DispatchGroup() + queue1.async(group: group) { + node1 = tuple.leftParen + } + queue2.async(group: group) { + node2 = tuple.leftParen + } + group.wait() + + let final = tuple.leftParen + + XCTAssertNotNil(node1) + XCTAssertNotNil(node2) + XCTAssertEqual(node1, node2) + XCTAssertEqual(node1, final) + XCTAssertEqual(node2, final) + } + +} diff --git a/Tests/SwiftSyntaxTest/ParseFile.swift b/Tests/SwiftSyntaxTest/ParseFile.swift new file mode 100644 index 00000000000..fc71e8c8b1f --- /dev/null +++ b/Tests/SwiftSyntaxTest/ParseFile.swift @@ -0,0 +1,30 @@ +import XCTest +import SwiftSyntax + +fileprivate struct Foo { + public let x: Int + private(set) var y: [Bool] +} + +#if os(macOS) +fileprivate class Test: NSObject { + @objc var bar: Int = 0 + func test() { + print(#selector(function)) + print(#keyPath(bar)) + } + @objc func function() { + } +} +#endif + +public class ParseFileTestCase: XCTestCase { + public func testParseSingleFile() { + let currentFile = URL(fileURLWithPath: #file) + XCTAssertNoThrow(try { + let fileContents = try String(contentsOf: currentFile) + let parsed = try SyntaxTreeParser.parse(currentFile) + XCTAssertEqual("\(parsed)", fileContents) + }()) + } +} diff --git a/Tests/SwiftSyntaxTest/SyntaxChildren.swift b/Tests/SwiftSyntaxTest/SyntaxChildren.swift new file mode 100644 index 00000000000..c99140179d3 --- /dev/null +++ b/Tests/SwiftSyntaxTest/SyntaxChildren.swift @@ -0,0 +1,32 @@ +import XCTest +import SwiftSyntax + +public class SyntaxChildrenAPITestCase: XCTestCase { + public func testIterateWithAllPresent() { + let returnStmt = SyntaxFactory.makeReturnStmt( + returnKeyword: SyntaxFactory.makeReturnKeyword(), + expression: SyntaxFactory.makeBlankUnknownExpr()) + + var iterator = returnStmt.children.makeIterator() + XCTAssertNext(&iterator) { ($0 as? TokenSyntax)?.tokenKind == .returnKeyword } + XCTAssertNext(&iterator) { $0 is ExprSyntax } + XCTAssertNextIsNil(&iterator) + } + + public func testIterateWithSomeMissing() { + let returnStmt = SyntaxFactory.makeReturnStmt( + returnKeyword: SyntaxFactory.makeReturnKeyword(), + expression: nil) + + var iterator = returnStmt.children.makeIterator() + XCTAssertNext(&iterator) { ($0 as? TokenSyntax)?.tokenKind == .returnKeyword } + XCTAssertNextIsNil(&iterator) + } + + public func testIterateWithAllMissing() { + let returnStmt = SyntaxFactory.makeBlankReturnStmt() + + var iterator = returnStmt.children.makeIterator() + XCTAssertNextIsNil(&iterator) + } +} diff --git a/Tests/SwiftSyntaxTest/SyntaxCollections.swift b/Tests/SwiftSyntaxTest/SyntaxCollections.swift new file mode 100644 index 00000000000..44a0cbe7ac9 --- /dev/null +++ b/Tests/SwiftSyntaxTest/SyntaxCollections.swift @@ -0,0 +1,104 @@ +import XCTest +import SwiftSyntax + +fileprivate func integerLiteralElement(_ int: Int) -> ArrayElementSyntax { + let literal = SyntaxFactory.makeIntegerLiteral("\(int)") + return SyntaxFactory.makeArrayElement( + expression: SyntaxFactory.makeIntegerLiteralExpr(digits: literal), + trailingComma: nil) +} + +public class SyntaxCollectionsAPITestCase: XCTestCase { + public func testAppendingElement() { + let arrayElementList = SyntaxFactory.makeArrayElementList([ + integerLiteralElement(0) + ]) + + let newArrayElementList = arrayElementList.appending(integerLiteralElement(1)) + + XCTAssertEqual(newArrayElementList.count, 2) + XCTAssertNotNil(newArrayElementList.child(at: 1)) + XCTAssertEqual("\(newArrayElementList.child(at: 1)!)", "1") + } + + public func testInsertingElement() { + let arrayElementList = SyntaxFactory.makeArrayElementList([ + integerLiteralElement(1) + ]) + + var newArrayElementList = arrayElementList.inserting(integerLiteralElement(0), at: 0) + + XCTAssertEqual(newArrayElementList.count, 2) + XCTAssertNotNil(newArrayElementList.child(at: 0)) + XCTAssertEqual("\(newArrayElementList.child(at: 0)!)", "0") + + newArrayElementList = newArrayElementList.inserting(integerLiteralElement(2), at: 2) + + XCTAssertEqual(newArrayElementList.count, 3) + XCTAssertNotNil(newArrayElementList.child(at: 2)) + XCTAssertEqual("\(newArrayElementList.child(at: 2)!)", "2") + } + + public func testPrependingElement() { + let arrayElementList = SyntaxFactory.makeArrayElementList([ + integerLiteralElement(1) + ]) + + let newArrayElementList = arrayElementList.prepending(integerLiteralElement(0)) + + XCTAssertEqual(newArrayElementList.count, 2) + XCTAssertNotNil(newArrayElementList.child(at: 0)) + XCTAssertEqual("\(newArrayElementList.child(at: 0)!)", "0") + } + + public func testRemovingFirstElement() { + let arrayElementList = SyntaxFactory.makeArrayElementList([ + integerLiteralElement(0), + integerLiteralElement(1) + ]) + + let newArrayElementList = arrayElementList.removingFirst() + + XCTAssertEqual(newArrayElementList.count, 1) + XCTAssertNotNil(newArrayElementList.child(at: 0)) + XCTAssertEqual("\(newArrayElementList.child(at: 0)!)", "1") + } + + public func testRemovingLastElement() { + let arrayElementList = SyntaxFactory.makeArrayElementList([ + integerLiteralElement(0), + integerLiteralElement(1) + ]) + + let newArrayElementList = arrayElementList.removingLast() + + XCTAssertEqual(newArrayElementList.count, 1) + XCTAssertNotNil(newArrayElementList.child(at: 0)) + XCTAssertEqual("\(newArrayElementList.child(at: 0)!)", "0") + } + + public func testRemovingElement() { + let arrayElementList = SyntaxFactory.makeArrayElementList([ + integerLiteralElement(0) + ]) + + let newArrayElementList = arrayElementList.removing(childAt: 0) + + XCTAssertEqual(newArrayElementList.count, 0) + XCTAssertNil(newArrayElementList.child(at: 0)) + } + + public func testReplacingElement() { + let arrayElementList = SyntaxFactory.makeArrayElementList([ + integerLiteralElement(0), + integerLiteralElement(1), + integerLiteralElement(2) + ]) + + let newArrayElementList = arrayElementList.replacing(childAt: 2, + with: integerLiteralElement(3)) + + XCTAssertNotNil(newArrayElementList.child(at: 2)) + XCTAssertEqual("\(newArrayElementList.child(at: 2)!)", "3") + } +} diff --git a/Tests/SwiftSyntaxTest/SyntaxFactory.swift b/Tests/SwiftSyntaxTest/SyntaxFactory.swift new file mode 100644 index 00000000000..5ede24919d0 --- /dev/null +++ b/Tests/SwiftSyntaxTest/SyntaxFactory.swift @@ -0,0 +1,111 @@ +import XCTest +import SwiftSyntax + +fileprivate func cannedStructDecl() -> StructDeclSyntax { + let structKW = SyntaxFactory.makeStructKeyword(trailingTrivia: .spaces(1)) + let fooID = SyntaxFactory.makeIdentifier("Foo", trailingTrivia: .spaces(1)) + let rBrace = SyntaxFactory.makeRightBraceToken(leadingTrivia: .newlines(1)) + let members = MemberDeclBlockSyntax { + $0.useLeftBrace(SyntaxFactory.makeLeftBraceToken()) + $0.useRightBrace(rBrace) + } + return StructDeclSyntax { + $0.useStructKeyword(structKW) + $0.useIdentifier(fooID) + $0.useMembers(members) + } +} + +public class SyntaxFactoryAPITestCase: XCTestCase { + public func testGenerated() { + + let structDecl = cannedStructDecl() + + XCTAssertEqual("\(structDecl)", + """ + struct Foo { + } + """) + + let forType = SyntaxFactory.makeIdentifier("for", + leadingTrivia: .backticks(1), + trailingTrivia: [ + .backticks(1), .spaces(1) + ]) + let newBrace = SyntaxFactory.makeRightBraceToken(leadingTrivia: .newlines(2)) + + let renamed = structDecl.withIdentifier(forType) + .withMembers(structDecl.members + .withRightBrace(newBrace)) + + XCTAssertEqual("\(renamed)", + """ + struct `for` { + + } + """) + + XCTAssertNotEqual(structDecl.members, renamed.members) + XCTAssertEqual(structDecl, structDecl.root as? StructDeclSyntax) + XCTAssertNil(structDecl.parent) + XCTAssertNotNil(structDecl.members.parent) + XCTAssertEqual(structDecl.members.parent as? StructDeclSyntax, structDecl) + + // Ensure that accessing children via named identifiers is exactly the + // same as accessing them as their underlying data. + XCTAssertEqual(structDecl.members, + structDecl.child(at: 7) as? MemberDeclBlockSyntax) + + XCTAssertEqual("\(structDecl.members.rightBrace)", + """ + + } + """) + } + + public func testTokenSyntax() { + let tok = SyntaxFactory.makeStructKeyword() + XCTAssertEqual("\(tok)", "struct") + XCTAssertTrue(tok.isPresent) + + let preSpacedTok = tok.withLeadingTrivia(.spaces(3)) + XCTAssertEqual("\(preSpacedTok)", " struct") + + let postSpacedTok = tok.withTrailingTrivia(.spaces(6)) + XCTAssertEqual("\(postSpacedTok)", "struct ") + + let prePostSpacedTok = preSpacedTok.withTrailingTrivia(.spaces(4)) + XCTAssertEqual("\(prePostSpacedTok)", " struct ") + } + + public func testFunctionCallSyntaxBuilder() { + let string = SyntaxFactory.makeStringLiteralExpr("Hello, world!") + let printID = SyntaxFactory.makeVariableExpr("print") + let arg = FunctionCallArgumentSyntax { + $0.useExpression(string) + } + let call = FunctionCallExprSyntax { + $0.useCalledExpression(printID) + $0.useLeftParen(SyntaxFactory.makeLeftParenToken()) + $0.addFunctionCallArgument(arg) + $0.useRightParen(SyntaxFactory.makeRightParenToken()) + } + XCTAssertEqual("\(call)", "print(\"Hello, world!\")") + + let terminatorArg = FunctionCallArgumentSyntax { + $0.useLabel(SyntaxFactory.makeIdentifier("terminator")) + $0.useColon(SyntaxFactory.makeColonToken(trailingTrivia: .spaces(1))) + $0.useExpression(SyntaxFactory.makeStringLiteralExpr(" ")) + } + let callWithTerminator = call.withArgumentList( + SyntaxFactory.makeFunctionCallArgumentList([ + arg.withTrailingComma( + SyntaxFactory.makeCommaToken(trailingTrivia: .spaces(1))), + terminatorArg + ]) + ) + + XCTAssertEqual("\(callWithTerminator)", + "print(\"Hello, world!\", terminator: \" \")") + } +} diff --git a/Tests/SwiftSyntaxTest/TestUtils.swift b/Tests/SwiftSyntaxTest/TestUtils.swift new file mode 100644 index 00000000000..86b36ec1b2a --- /dev/null +++ b/Tests/SwiftSyntaxTest/TestUtils.swift @@ -0,0 +1,27 @@ +import Foundation +import SwiftSyntax +import XCTest + +func getInput(_ file: String) -> URL { + var result = URL(fileURLWithPath: #file) + result.deleteLastPathComponent() + result.appendPathComponent("Inputs") + result.appendPathComponent(file) + return result +} + +/// Verifies that there is a next item returned by the iterator and that it +/// satisfies the given predicate. +func XCTAssertNext( + _ iterator: inout Iterator, + satisfies predicate: (Iterator.Element) throws -> Bool + ) rethrows { + let next = iterator.next() + XCTAssertNotNil(next) + XCTAssertTrue(try predicate(next!)) +} + +/// Verifies that the iterator is exhausted. +func XCTAssertNextIsNil(_ iterator: inout Iterator) { + XCTAssertNil(iterator.next()) +} diff --git a/Tests/SwiftSyntaxTest/VisitorTest.swift b/Tests/SwiftSyntaxTest/VisitorTest.swift new file mode 100644 index 00000000000..0cf0714e601 --- /dev/null +++ b/Tests/SwiftSyntaxTest/VisitorTest.swift @@ -0,0 +1,78 @@ +import XCTest +import SwiftSyntax + +public class SyntaxVisitorTestCase: XCTestCase { + public func testBasic() { + class FuncCounter: SyntaxVisitor { + var funcCount = 0 + override func visit(_ node: FunctionDeclSyntax) { + funcCount += 1 + super.visit(node) + } + } + XCTAssertNoThrow(try { + let parsed = try SyntaxTreeParser.parse(getInput("visitor.swift")) + let counter = FuncCounter() + let hashBefore = parsed.hashValue + counter.visit(parsed) + XCTAssertEqual(counter.funcCount, 3) + XCTAssertEqual(hashBefore, parsed.hashValue) + }()) + } + + public func testRewritingNodeWithEmptyChild() { + class ClosureRewriter: SyntaxRewriter { + override func visit(_ node: ClosureExprSyntax) -> ExprSyntax { + // Perform a no-op transform that requires rebuilding the node. + return node.withSignature(node.signature) + } + } + XCTAssertNoThrow(try { + let parsed = try SyntaxTreeParser.parse(getInput("closure.swift")) + let rewriter = ClosureRewriter() + let rewritten = rewriter.visit(parsed) + XCTAssertEqual(parsed.description, rewritten.description) + }()) + } + + public func testSyntaxRewriterVisitAny() { + class VisitAnyRewriter: SyntaxRewriter { + let transform: (TokenSyntax) -> TokenSyntax + init(transform: @escaping (TokenSyntax) -> TokenSyntax) { + self.transform = transform + } + override func visitAny(_ node: Syntax) -> Syntax? { + if let tok = node as? TokenSyntax { + return transform(tok) + } + return nil + } + } + XCTAssertNoThrow(try { + let parsed = try SyntaxTreeParser.parse(getInput("near-empty.swift")) + let rewriter = VisitAnyRewriter(transform: { _ in + return SyntaxFactory.makeIdentifier("") + }) + let rewritten = rewriter.visit(parsed) + XCTAssertEqual(rewritten.description, "") + }()) + } + + public func testSyntaxRewriterVisitCollection() { + class VisitCollections: SyntaxVisitor { + var numberOfCodeBlockItems = 0 + + override func visit(_ items: CodeBlockItemListSyntax) { + numberOfCodeBlockItems += items.count + super.visit(items) + } + } + + XCTAssertNoThrow(try { + let parsed = try SyntaxTreeParser.parse(getInput("nested-blocks.swift")) + let visitor = VisitCollections() + visitor.visit(parsed) + XCTAssertEqual(4, visitor.numberOfCodeBlockItems) + }()) + } +} diff --git a/build-script.py b/build-script.py new file mode 100755 index 00000000000..ef0b56d9693 --- /dev/null +++ b/build-script.py @@ -0,0 +1,384 @@ +#!/usr/bin/env python + +from __future__ import print_function + +import argparse +import os +import subprocess +import sys +import tempfile + + +PACKAGE_DIR = os.path.dirname(os.path.realpath(__file__)) +WORKSPACE_DIR = os.path.realpath(PACKAGE_DIR + '/..') + +INCR_TRANSFER_ROUNDTRIP_EXEC = \ + WORKSPACE_DIR + '/swift/utils/incrparse/incr_transfer_round_trip.py' +GYB_EXEC = WORKSPACE_DIR + '/swift/utils/gyb' +LIT_EXEC = WORKSPACE_DIR + '/llvm/utils/lit/lit.py' + +### Generic helper functions + +def printerr(message): + print(message, file=sys.stderr) + + +def fatal_error(message): + printerr(message) + sys.exit(1) + +def escapeCmdArg(arg): + if '"' in arg or ' ' in arg: + return '"%s"' % arg.replace('"', '\\"') + else: + return arg + + +def call(cmd, env=os.environ, stdout=None, stderr=subprocess.STDOUT, + verbose=False): + if verbose: + print(' '.join([escapeCmdArg(arg) for arg in cmd])) + process = subprocess.Popen(cmd, env=env, stdout=stdout, stderr=stderr) + process.wait() + + return process.returncode + + +def check_call(cmd, verbose=False): + if verbose: + print(' '.join([escapeCmdArg(arg) for arg in cmd])) + return subprocess.call(cmd, stderr=subprocess.STDOUT) + + +def realpath(path): + if path is None: + return None + return os.path.realpath(path) + + +### Build phases + +## Generating gyb files + +def check_gyb_exec(): + if not os.path.exists(GYB_EXEC): + fatal_error(''' +Error: Could not find gyb. + +Make sure you have the main swift repo checked out next to the swift-syntax +repository. +Refer to README.md for more information. +''') + + +def check_rsync(): + with open(os.devnull, 'w') as DEVNULL: + if call(['rsync', '--version'], stdout=DEVNULL) != 0: + fatal_error('Error: Could not find rsync.') + + +def generate_gyb_files(verbose): + print('** Generating gyb Files **') + + check_gyb_exec() + check_rsync() + + swiftsyntax_sources_dir = PACKAGE_DIR + '/Sources/SwiftSyntax' + temp_files_dir = tempfile.gettempdir() + generated_files_dir = swiftsyntax_sources_dir + '/gyb_generated' + + if not os.path.exists(temp_files_dir): + os.makedirs(temp_files_dir) + if not os.path.exists(generated_files_dir): + os.makedirs(generated_files_dir) + + # Generate the new .swift files in a temporary directory and only copy them + # to Sources/SwiftSyntax/gyb_generated if they are different than the files + # already residing there. This way we don't touch the generated .swift + # files if they haven't changed and don't trigger a rebuild. + for gyb_file in os.listdir(swiftsyntax_sources_dir): + if not gyb_file.endswith('.gyb'): + continue + + # Slice off the '.gyb' to get the name for the output file + output_file_name = gyb_file[:-4] + + # Generate the new file + check_call([GYB_EXEC] + + [swiftsyntax_sources_dir + '/' + gyb_file] + + ['-o', temp_files_dir + '/' + output_file_name], + verbose=verbose) + + # Copy the file if different from the file already present in + # gyb_generated + check_call(['rsync'] + + ['--checksum'] + + [temp_files_dir + '/' + output_file_name] + + [generated_files_dir + '/' + output_file_name], + verbose=verbose) + + print('Done Generating gyb Files') + + +## Building swiftSyntax + +def get_swiftpm_invocation(spm_exec, build_dir, release): + if spm_exec == 'swift build': + swiftpm_call = ['swift', 'build'] + elif spm_exec == 'swift test': + swiftpm_call = ['swift', 'test'] + else: + swiftpm_call = [spm_exec] + + swiftpm_call.extend(['--package-path', PACKAGE_DIR]) + if release: + swiftpm_call.extend(['--configuration', 'release']) + if build_dir: + swiftpm_call.extend(['--build-path', build_dir]) + + return swiftpm_call + + +def build_swiftsyntax(swift_build_exec, build_dir, build_test_util, release, + verbose): + print('** Building SwiftSyntax **') + + swiftpm_call = get_swiftpm_invocation(spm_exec=swift_build_exec, + build_dir=build_dir, + release=release) + swiftpm_call.extend(['--product', 'SwiftSyntax']) + + # Only build lit-test-helper if we are planning to run tests + if build_test_util: + swiftpm_call.extend(['--product', 'lit-test-helper']) + + if verbose: + swiftpm_call.extend(['--verbose']) + + check_call(swiftpm_call, verbose=verbose) + + +## Testing + +def run_tests(swift_test_exec, build_dir, release, swift_build_exec, + filecheck_exec, swiftc_exec, swift_syntax_test_exec, verbose): + print('** Running SwiftSyntax Tests **') + + lit_success = run_lit_tests(swift_build_exec=swift_build_exec, + build_dir=build_dir, + release=release, + filecheck_exec=filecheck_exec, + swift_syntax_test_exec=swift_syntax_test_exec, + verbose=verbose) + if not lit_success: + return False + + xctest_success = run_xctests(swift_test_exec=swift_test_exec, + build_dir=build_dir, + release=release, + swiftc_exec=swiftc_exec, + verbose=verbose) + if not xctest_success: + return False + + return True + +# Lit-based tests + +def check_lit_exec(): + if not os.path.exists(LIT_EXEC): + fatal_error(''' +Error: Could not find lit.py. + +Make sure you have the llvm repo checked out next to the swift-syntax repo. +Refer to README.md for more information. +''') + + +def check_incr_transfer_roundtrip_exec(): + if not os.path.exists(INCR_TRANSFER_ROUNDTRIP_EXEC): + fatal_error(''' +Error: Could not find incr_transfer_round_trip.py. + +Make sure you have the main swift repo checked out next to the swift-syntax +repo. +Refer to README.md for more information. +''') + + +def find_lit_test_helper_exec(swift_build_exec, build_dir, release): + swiftpm_call = get_swiftpm_invocation(spm_exec=swift_build_exec, + build_dir=build_dir, + release=release) + swiftpm_call.extend(['--product', 'lit-test-helper']) + swiftpm_call.extend(['--show-bin-path']) + + bin_dir = subprocess.check_output(swiftpm_call, stderr=subprocess.STDOUT) + return bin_dir.strip() + '/lit-test-helper' + + +def run_lit_tests(swift_build_exec, build_dir, release, filecheck_exec, + swift_syntax_test_exec, verbose): + print('** Running lit-based tests **') + + check_lit_exec() + check_incr_transfer_roundtrip_exec() + + lit_test_helper_exec = \ + find_lit_test_helper_exec(swift_build_exec=swift_build_exec, + build_dir=build_dir, + release=release) + + lit_call = [LIT_EXEC] + lit_call.extend([PACKAGE_DIR + '/lit_tests']) + + if filecheck_exec: + lit_call.extend(['--param', 'FILECHECK=' + filecheck_exec]) + if lit_test_helper_exec: + lit_call.extend(['--param', 'LIT_TEST_HELPER=' + lit_test_helper_exec]) + if swift_syntax_test_exec: + lit_call.extend(['--param', 'SWIFT_SYNTAX_TEST=' + + swift_syntax_test_exec]) + lit_call.extend(['--param', 'INCR_TRANSFER_ROUND_TRIP.PY=' + + INCR_TRANSFER_ROUNDTRIP_EXEC]) + + # Print all failures + lit_call.extend(['--verbose']) + # Don't show all commands if verbose is not enabled + if not verbose: + lit_call.extend(['--succinct']) + + return call(lit_call, verbose=verbose) == 0 + + +## XCTest based tests + +def run_xctests(swift_test_exec, build_dir, release, swiftc_exec, verbose): + print('** Running XCTests **') + swiftpm_call = get_swiftpm_invocation(spm_exec=swift_test_exec, + build_dir=build_dir, + release=release) + + if verbose: + swiftpm_call.extend(['--verbose']) + + subenv = os.environ + if swiftc_exec: + # Add the swiftc exec to PATH so that SwiftSyntax finds it + subenv['PATH'] = realpath(swiftc_exec + '/..') + ':' + subenv['PATH'] + + return call(swiftpm_call, env=subenv, verbose=verbose) == 0 + + +### Main + +def main(): + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description=''' +Build and test script for SwiftSytnax. + +Build SwiftSyntax by generating all necessary files form the corresponding +.swift.gyb files first. For this, SwiftSyntax needs to be check out alongside +the main swift repo (http://github.com/apple/swift/) in the following structure +- (containing directory) + - swift + - swift-syntax +It is not necessary to build the compiler project. + +The build script can also drive the test suite included in the SwiftSyntax +repo. This requires a custom build of the compiler project since it accesses +test utilities that are not shipped as part of the toolchains. See the Testing +section for arguments that need to be specified for this. +''') + + basic_group = parser.add_argument_group('Basic') + + basic_group.add_argument('--build-dir', default=None, help=''' + The directory in which build products shall be put. If omitted a + directory named '.build' will be put in the swift-syntax directory. + ''') + basic_group.add_argument('-v', '--verbose', action='store_true', help=''' + Enable extensive logging of executed steps. + ''') + basic_group.add_argument('-r', '--release', action='store_true', help=''' + Build as a release build. + ''') + + testing_group = parser.add_argument_group('Testing') + testing_group.add_argument('-t', '--test', action='store_true', + help='Run tests') + + testing_group.add_argument('--swift-build-exec', default='swift build', + help=''' + Path to the swift-build executable that is used to build SwiftPM projects + If not specified the the 'swift build' command will be used. + ''') + testing_group.add_argument('--swift-test-exec', default='swift test', + help=''' + Path to the swift-test executable that is used to test SwiftPM projects + If not specified the the 'swift test' command will be used. + ''') + testing_group.add_argument('--swiftc-exec', default='swiftc', help=''' + Path to the swift executable. If not specified the swiftc exeuctable + will be inferred from PATH. + ''') + testing_group.add_argument('--swift-syntax-test-exec', default=None, + help=''' + Path to the swift-syntax-test executable that was built from the main + Swift repo. If not specified, it will be looked up from PATH. + ''') + testing_group.add_argument('--filecheck-exec', default=None, help=''' + Path to the FileCheck executable that was built as part of the LLVM + repository. If not specified, it will be looked up from PATH. + ''') + + args = parser.parse_args(sys.argv[1:]) + + + try: + generate_gyb_files(args.verbose) + except subprocess.CalledProcessError as e: + printerr('Error: Generating .gyb files failed') + printerr('Executing: %s' % ' '.join(e.cmd)) + printerr(e.output) + sys.exit(1) + + try: + build_swiftsyntax(swift_build_exec=args.swift_build_exec, + build_dir=args.build_dir, + build_test_util=args.test, + release=args.release, + verbose=args.verbose) + except subprocess.CalledProcessError as e: + printerr('Error: Building SwiftSyntax failed') + printerr('Executing: %s' % ' '.join(e.cmd)) + printerr(e.output) + sys.exit(1) + + if args.test: + try: + success = run_tests(swift_test_exec=args.swift_test_exec, + build_dir=realpath(args.build_dir), + release=args.release, + swift_build_exec=args.swift_build_exec, + filecheck_exec=realpath(args.filecheck_exec), + swiftc_exec=realpath(args.swiftc_exec), + swift_syntax_test_exec= + realpath(args.swift_syntax_test_exec), + verbose=args.verbose) + if not success: + # An error message has already been printed by the failing test + # suite + sys.exit(1) + else: + print('** All tests passed **') + except subprocess.CalledProcessError as e: + printerr('Error: Running tests failed') + printerr('Executing: %s' % ' '.join(e.cmd)) + printerr(e.output) + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/lit_tests/coloring.swift b/lit_tests/coloring.swift new file mode 100644 index 00000000000..ffdfb30b572 --- /dev/null +++ b/lit_tests/coloring.swift @@ -0,0 +1,396 @@ +// RUN: %lit-test-helper classify-syntax -source-file %s | %FileCheck %s + +enum List { + case Nil + // rdar://21927124 + // CHECK: indirect case Cons(T, List) + indirect case Cons(T, List) +} + +// CHECK: struct S { +struct S { + // CHECK: var x : Int + var x : Int + // CHECK: var y : Int.Int + var y : Int.Int + // CHECK: var a, b : Int + var a, b : Int +} + +enum EnumWithDerivedEquatableConformance : Int { +// CHECK-LABEL: enum EnumWithDerivedEquatableConformance : {{()}}Int{{()?}} { + case CaseA +// CHECK-NEXT: case CaseA + case CaseB, CaseC +// CHECK-NEXT: case CaseB, CaseC + case CaseD = 30, CaseE +// CHECK-NEXT: case CaseD = 30, CaseE +} +// CHECK-NEXT: } + +// CHECK: class MyCls { +class MyCls { + // CHECK: var www : Int + var www : Int + + // CHECK: func foo(x: Int) {} + func foo(x: Int) {} + // CHECK: var aaa : Int { + var aaa : Int { + // CHECK: get {} + get {} + // CHECK: set {} + set {} + } + // CHECK: var bbb : Int { + var bbb : Int { + // CHECK: set { + set { + // CHECK: var tmp : Int + var tmp : Int + } + // CHECK: get { + get { + // CHECK: var tmp : Int + var tmp : Int + } + } + + // CHECK: subscript (i : Int, j : Int) -> Int { + subscript (i : Int, j : Int) -> Int { + // CHECK: get { + get { + // CHECK: return i + j + return i + j + } + // CHECK: set(v) { + set(v) { + // CHECK: v + i - j + v + i - j + } + } + + // CHECK: func multi(_ name: Int, otherpart x: Int) {} + func multi(_ name: Int, otherpart x: Int) {} +} + +// CHECK-LABEL: class Attributes { +class Attributes { +// CHECK: @IBOutlet var v0: Int + @IBOutlet var v0: Int + +// CHECK: @IBOutlet @IBOutlet var v1: String + @IBOutlet @IBOutlet var v1: String + +// CHECK: @objc @IBOutlet var v2: String + @objc @IBOutlet var v2: String + +// CHECK: @IBOutlet @objc var v3: String + @IBOutlet @objc var v3: String + +// CHECK: @available(*, unavailable) func f1() {} + @available(*, unavailable) func f1() {} + +// CHECK: @available(*, unavailable) @IBAction func f2() {} + @available(*, unavailable) @IBAction func f2() {} + +// CHECK: @IBAction @available(*, unavailable) func f3() {} + @IBAction @available(*, unavailable) func f3() {} + +// CHECK: mutating func func_mutating_1() {} + mutating func func_mutating_1() {} + +// CHECK: nonmutating func func_mutating_2() {} + nonmutating func func_mutating_2() {} +} + +func stringLikeLiterals() { +// CHECK: var us1: UnicodeScalar = "a" + var us1: UnicodeScalar = "a" +// CHECK: var us2: UnicodeScalar = "ы" + var us2: UnicodeScalar = "ы" + +// CHECK: var ch1: Character = "a" + var ch1: Character = "a" +// CHECK: var ch2: Character = "あ" + var ch2: Character = "あ" + +// CHECK: var s1 = "abc абвгд あいうえお" + var s1 = "abc абвгд あいうえお" +} + +// CHECK: var globComp : Int +var globComp : Int { + // CHECK: get { + get { + // CHECK: return 0 + return 0 + } +} + +// CHECK: func foo(n: Float) -> Int { +func foo(n: Float) -> Int { + // CHECK: var fnComp : Int + var fnComp : Int { + // CHECK: get { + get { + // CHECK: var a: Int + // CHECK: return 0 + var a: Int + return 0 + } + } + // CHECK: var q = {{()?}}MyCls{{()?}}() + var q = MyCls() + // CHECK: var ee = "yoo"; + var ee = "yoo"; + // CHECK: return 100009 + return 100009 +} + +// CHECK: protocol Prot +protocol Prot { + // CHECK: typealias Blarg + typealias Blarg + // CHECK: func protMeth(x: Int) + func protMeth(x: Int) + // CHECK: var protocolProperty1: Int { get } + var protocolProperty1: Int { get } + // CHECK: var protocolProperty2: Int { get set } + var protocolProperty2: Int { get set } +} + +// CHECK: infix operator *-* : FunnyPrecedence{{$}} +infix operator *-* : FunnyPrecedence + +// CHECK: precedencegroup FunnyPrecedence +// CHECK-NEXT: associativity: left{{$}} +// CHECK-NEXT: higherThan: MultiplicationPrecedence +precedencegroup FunnyPrecedence { + associativity: left + higherThan: MultiplicationPrecedence +} + +// CHECK: func *-*(l: Int, r: Int) -> Int { return l }{{$}} +func *-*(l: Int, r: Int) -> Int { return l } + +// CHECK: infix operator *-+* : FunnyPrecedence +infix operator *-+* : FunnyPrecedence + +// CHECK: func *-+*(l: Int, r: Int) -> Int { return l }{{$}} +func *-+*(l: Int, r: Int) -> Int { return l } + +// CHECK: infix operator *--*{{$}} +infix operator *--* + +// CHECK: func *--*(l: Int, r: Int) -> Int { return l }{{$}} +func *--*(l: Int, r: Int) -> Int { return l } + +// CHECK: protocol Prot2 : Prot {} +protocol Prot2 : Prot {} + +// CHECK: class SubCls : MyCls, Prot {} +class SubCls : MyCls, Prot {} + +// CHECK: func genFnProt where T.Blarg : Prot2>(_: T) -> Int {}{{$}} +func genFn(_: T) -> Int {} + +func f(x: Int) -> Int { + // CHECK: "This is string \(genFn({(a:Int -> Int) in a})) interpolation" + "This is string \(genFn({(a:Int -> Int) in a})) interpolation" + + // CHECK: "This is unterminated + "This is unterminated + + // CHECK: "This is unterminated with ignored \(interpolation) in it + "This is unterminated with ignored \(interpolation) in it + + // CHECK: "This is terminated with invalid \(interpolation" + "in it" + "This is terminated with invalid \(interpolation" + "in it" + + // CHECK: """ + // CHECK-NEXT: This is a multiline string. + // CHECK-NEXT: """ + """ + This is a multiline string. +""" + + // CHECK: """ + // CHECK-NEXT: This is a multiline\( "interpolated" )string + // CHECK-NEXT: \( + // CHECK-NEXT: """ + // CHECK-NEXT: inner + // CHECK-NEXT: """ + // CHECK-NEXT: ) + // CHECK-NEXT: """ + """ + This is a multiline\( "interpolated" )string + \( + """ + inner + """ + ) + """ + + // CHECK: "\(1)\(1)" + "\(1)\(1)" +} + +// CHECK: func bar(x: Int) -> (Int, Float) { +func bar(x: Int) -> (Int, Float) { + // CHECK: foo({{()?}}Float{{()?}}()) + foo(Float()) +} + +class GenC {} + +func test() { + // CHECK: {{()?}}GenC{{()?}}<Int, Float>() + var x = GenC() +} + +// CHECK: typealias MyInt = Int +typealias MyInt = Int + +func test2(x: Int) { + // CHECK: "\(x)" + "\(x)" +} + +// CHECK: class Observers { +class Observers { + // CHECK: var p1 : Int { + var p1 : Int { + // CHECK: willSet(newValue) {} + willSet(newValue) {} + // CHECK: didSet {} + didSet {} + } + // CHECK: var p2 : Int { + var p2 : Int { + // CHECK: didSet {} + didSet {} + // CHECK: willSet {} + willSet {} + } +} + +// CHECK: func test3(o: AnyObject) { +func test3(o: AnyObject) { + // CHECK: _ = o is MyCls ? o as MyCls : o as! MyCls as MyCls + 1 + _ = o is MyCls ? o as MyCls : o as! MyCls as MyCls + 1 +} + +// CHECK: class MySubClass : MyCls { +class MySubClass : MyCls { + // CHECK: override func foo(x: Int) {} + override func foo(x: Int) {} + + // CHECK: convenience init(a: Int) {} + convenience init(a: Int) {} +} + +// CHECK: var g1 = { (x: Int) -> Int in return 0 } +var g1 = { (x: Int) -> Int in return 0 } + +// CHECK: infix operator ~~ { +infix operator ~~ {} +// CHECK: prefix operator *~~ { +prefix operator *~~ {} +// CHECK: postfix operator ~~* { +postfix operator ~~* {} + +func test_defer() { + defer { + // CHECK: let x : Int = 0 + let x : Int = 0 + } +} + +func test6(x: T) {} +// CHECK: func test6Prot>(x: T) {}{{$}} + +// CHECK: func <#test1#> () {} +func <#test1#> () {} + +func funcTakingFor(for internalName: Int) {} +// CHECK: func funcTakingFor(for internalName: Int) {} + +func funcTakingIn(in internalName: Int) {} +// CHECK: func funcTakingIn(in internalName: Int) {} + +_ = 123 +// CHECK: 123 +_ = -123 +// CHECK: -123 +_ = -1 +// CHECK: -1 +_ = -0x123 +// CHECK: -0x123 +_ = -3.1e-5 +// CHECK: 3.1e-5 + +"--\"\(x) --" +// CHECK: "--\"\(x) --" + +func keywordAsLabel1(in: Int) {} +// CHECK: func keywordAsLabel1(in: Int) {} +func keywordAsLabel2(for: Int) {} +// CHECK: func keywordAsLabel2(for: Int) {} +func keywordAsLabel3(if: Int, for: Int) {} +// CHECK: func keywordAsLabel3(if: Int, for: Int) {} +func keywordAsLabel4(_: Int) {} +// CHECK: func keywordAsLabel4(_: Int) {} +func keywordAsLabel5(_: Int, for: Int) {} +// CHECK: func keywordAsLabel5(_: Int, for: Int) {} +func keywordAsLabel6(if func: Int) {} +// CHECK: func keywordAsLabel6(if func: Int) {} + +func foo1() { +// CHECK: func foo1() { + keywordAsLabel1(in: 1) +// CHECK: keywordAsLabel1(in: 1) + keywordAsLabel2(for: 1) +// CHECK: keywordAsLabel2(for: 1) + keywordAsLabel3(if: 1, for: 2) +// CHECK: keywordAsLabel3(if: 1, for: 2) + keywordAsLabel5(1, for: 2) +// CHECK: keywordAsLabel5(1, for: 2) + + _ = (if: 0, for: 2) +// CHECK: _ = (if: 0, for: 2) + _ = (_: 0, _: 2) +// CHECK: _ = (_: 0, _: 2) +} + +func foo2(O1 : Int?, O2: Int?, O3: Int?) { + guard let _ = O1, var _ = O2, let _ = O3 else { } +// CHECK: guard let _ = O1, var _ = O2, let _ = O3 else { } + if let _ = O1, var _ = O2, let _ = O3 {} +// CHECK: if let _ = O1, var _ = O2, let _ = O3 {} +} + +func keywordInCaseAndLocalArgLabel(_ for: Int, for in: Int, class _: Int) { +// CHECK: func keywordInCaseAndLocalArgLabel(_ for: Int, for in: Int, class _: Int) { + switch(`for`, `in`) { + case (let x, let y): +// CHECK: case (let x, let y): + print(x, y) + } +} + +// CHECK: class Ownership { +class Ownership { + // CHECK: weak var w + weak var w + // CHECK: unowned var u + unowned var u + // CHECK: unowned(unsafe) var uu + unowned(unsafe) var uu +} +// FIXME: CHECK: let closure = { [weak x=bindtox, unowned y=bindtoy, unowned(unsafe) z=bindtoz] in } +let closure = { [weak x=bindtox, unowned y=bindtoy, unowned(unsafe) z=bindtoz] in } + +protocol FakeClassRestrictedProtocol : `class` {} +// FIXME: rdar://42801404: OLD and NEW should be the same '`class`'. +// CHECK: protocol FakeClassRestrictedProtocol : `class` {} diff --git a/lit_tests/coloring_comments.swift b/lit_tests/coloring_comments.swift new file mode 100644 index 00000000000..4b9aadfbd12 --- /dev/null +++ b/lit_tests/coloring_comments.swift @@ -0,0 +1,233 @@ +// RUN: %lit-test-helper classify-syntax -source-file %s | %FileCheck %s + +// CHECK: /* foo is the best */ +/* foo is the best */ +func foo(n: Float) {} + +///- returns: single-line, no space +// CHECK: ///- returns: single-line, no space + +/// - returns: single-line, 1 space +// CHECK: /// - returns: single-line, 1 space + +/// - returns: single-line, 2 spaces +// CHECK: /// - returns: single-line, 2 spaces + +/// - returns: single-line, more spaces +// CHECK: /// - returns: single-line, more spaces + +// CHECK: protocol Prot +protocol Prot {} + +func f(x: Int) -> Int { + // CHECK: // string interpolation is the best + // string interpolation is the best + "This is string \(x) interpolation" +} + +// FIXME: blah. +// FIXME: blah blah +// Something something, FIXME: blah + +// CHECK: // FIXME: blah. +// CHECK: // FIXME: blah blah +// CHECK: // Something something, FIXME: blah + + + +/* FIXME: blah*/ + +// CHECK: /* FIXME: blah*/ + +/* + * FIXME: blah + * Blah, blah. + */ + +// CHECK: /* +// CHECK: * FIXME: blah +// CHECK: * Blah, blah. +// CHECK: */ + +// TODO: blah. +// TTODO: blah. +// MARK: blah. + +// CHECK: // TODO: blah. +// CHECK: // TTODO: blah. +// CHECK: // MARK: blah. + +// CHECK: func test5() -> Int { +func test5() -> Int { + // CHECK: // TODO: something, something. + // TODO: something, something. + // CHECK: return 0 + return 0 +} + +// http://whatever.com?ee=2&yy=1 and radar://123456 +/* http://whatever.com FIXME: see in http://whatever.com/fixme + http://whatever.com */ + +// CHECK: // http://whatever.com?ee=2&yy=1 and radar://123456 +// CHECK: /* http://whatever.com FIXME: see in http://whatever.com/fixme +// CHECK: http://whatever.com */ + +// CHECK: // http://whatever.com/what-ever +// http://whatever.com/what-ever + +/// Brief. +/// +/// Simple case. +/// +/// - parameter x: A number +/// - parameter y: Another number +/// - PaRamEteR z-hyphen-q: Another number +/// - parameter : A strange number... +/// - parameternope1: Another number +/// - parameter nope2 +/// - parameter: nope3 +/// -parameter nope4: Another number +/// * parameter nope5: Another number +/// - parameter nope6: Another number +/// - Parameters: nope7 +/// - seealso: yes +/// - seealso: yes +/// - seealso: +/// -seealso: nope +/// - seealso : nope +/// - seealso nope +/// - returns: `x + y` +func foo(x: Int, y: Int) -> Int { return x + y } +// CHECK: /// Brief. +// CHECK: /// +// CHECK: /// Simple case. +// CHECK: /// +// CHECK: /// - parameter x: A number +// CHECK: /// - parameter y: Another number +// CHECK: /// - PaRamEteR z-hyphen-q: Another number +// CHECK: /// - parameter : A strange number... +// CHECK: /// - parameternope1: Another number +// CHECK: /// - parameter nope2 +// CHECK: /// - parameter: nope3 +// CHECK: /// -parameter nope4: Another number +// CHECK: /// * parameter nope5: Another number +// CHECK: /// - parameter nope6: Another number +// CHECK: /// - Parameters: nope7 +// CHECK: /// - seealso: yes +// CHECK: /// - seealso: yes +// CHECK: /// - seealso: +// CHECK: /// -seealso: nope +// CHECK: /// - seealso : nope +// CHECK: /// - seealso nope +// CHECK: /// - returns: `x + y` +// CHECK: func foo(x: Int, y: Int) -> Int { return x + y } + + +/// Brief. +/// +/// Simple case. +/// +/// - Parameters: +/// - x: A number +/// - y: Another number +/// +///- note: NOTE1 +/// +/// - NOTE: NOTE2 +/// - note: Not a Note field (not at top level) +/// - returns: `x + y` +func bar(x: Int, y: Int) -> Int { return x + y } +// CHECK: /// Brief. +// CHECK: /// +// CHECK: /// Simple case. +// CHECK: /// +// CHECK: /// - Parameters: +// CHECK: /// - x: A number +// CHECK: /// - y: Another number +// CHECK: /// +// CHECK: ///- note: NOTE1 +// CHECK: /// +// CHECK: /// - NOTE: NOTE2 +// CHECK: /// - note: Not a Note field (not at top level) +// CHECK: /// - returns: `x + y` +// CHECK: func bar(x: Int, y: Int) -> Int { return x + y } + +/** + Does pretty much nothing. + + Not a parameter list: improper indentation. + - Parameters: sdfadsf + + - WARNING: - WARNING: Should only have one field + + - $$$: Not a field. + + Empty field, OK: +*/ +func baz() {} +// CHECK: /** +// CHECK: Does pretty much nothing. +// CHECK: Not a parameter list: improper indentation. +// CHECK: - Parameters: sdfadsf +// CHECK: - WARNING: - WARNING: Should only have one field +// CHECK: - $$$: Not a field. +// CHECK: Empty field, OK: +// CHECK: */ +// CHECK: func baz() {} + +/***/ +func emptyDocBlockComment() {} +// CHECK: /***/ +// CHECK: func emptyDocBlockComment() {} + +/** +*/ +func emptyDocBlockComment2() {} +// CHECK: /** +// CHECK: */ +// CHECK: func emptyDocBlockComment2() {} + +/** */ +func emptyDocBlockComment3() {} +// CHECK: /** */ +// CHECK: func emptyDocBlockComment3() {} + + +/**/ +func malformedBlockComment(f : () throws -> ()) rethrows {} +// CHECK: /**/ + +// CHECK: func malformedBlockComment(f : () throws -> ()) rethrows {} + +//: playground doc comment line +func playgroundCommentLine(f : () throws -> ()) rethrows {} +// CHECK: //: playground doc comment line + +/*: + playground doc comment multi-line +*/ +func playgroundCommentMultiLine(f : () throws -> ()) rethrows {} +// CHECK: /*: +// CHECK: playground doc comment multi-line +// CHECK: */ + +/// [strict weak ordering](http://en.wikipedia.org/wiki/Strict_weak_order#Strict_weak_orderings) +// CHECK: /// [strict weak ordering](http://en.wikipedia.org/wiki/Strict_weak_order#Strict_weak_orderings) + +/** aaa + + - returns: something + */ +// CHECK: - returns: something +let blah = 0 + +// Keep this as the last test +/** + Trailing off ... +func unterminatedBlockComment() {} +// CHECK: // Keep this as the last test +// CHECK: /** +// CHECK: Trailing off ... +// CHECK: func unterminatedBlockComment() {} +// CHECK: diff --git a/lit_tests/coloring_configs.swift b/lit_tests/coloring_configs.swift new file mode 100644 index 00000000000..9550960d6a2 --- /dev/null +++ b/lit_tests/coloring_configs.swift @@ -0,0 +1,377 @@ +// RUN: %lit-test-helper classify-syntax -source-file %s | %FileCheck %s + +// CHECK: var f : Int +var f : Int + +// CHECK: <#kw>#if <#id>os(<#id>macOS) +#if os(macOS) +#endif + +// CHECK: <#kw>#if <#id>CONF +#if CONF + // CHECK: var x : Int + var x : Int +// CHECK: <#kw>#else +#else + // CHECK: var x : Float + var x : Float +// CHECK: <#kw>#endif +#endif + +// CHECK: <#kw>#if <#id>CONF +#if CONF + // CHECK: var x2 : Int + var x2 : Int +// CHECK: <#kw>#endif +#endif + +// CHECK: <#kw>#if !<#id>CONF +#if !CONF + // CHECK: var x3 : Int + var x3 : Int +// CHECK: <#kw>#else +#else + // CHECK: var x3 : Float + var x3 : Float +// CHECK: <#kw>#endif +#endif + +// CHECK: <#kw>#if !<#id>CONF +#if !CONF + // CHECK: var x4 : Int + var x4 : Int +// CHECK: <#kw>#endif +#endif + +// CHECK: <#kw>#if <#id>CONF +#if CONF + // CHECK: var y1 : Int + var y1 : Int +// CHECK: <#kw>#elseif <#id>BAZ +#elseif BAZ + // CHECK: var y1 : String + var y1 : String +// CHECK: <#kw>#else +#else + // CHECK: var y1 : Float + var y1 : Float +// CHECK: <#kw>#endif +#endif + +// CHECK: <#kw>#if !<#id>CONF +#if !CONF + // CHECK: var y2 : Int + var y2 : Int +// CHECK: <#kw>#elseif <#id>BAZ +#elseif BAZ + // CHECK: var y2 : String + var y2 : String +// CHECK: <#kw>#else +#else + // CHECK: var y2 : Float + var y2 : Float +// CHECK: <#kw>#endif +#endif + +// CHECK: <#kw>#if !<#id>CONF +#if !CONF + // CHECK: var y3 : Int + var y3 : Int +// CHECK: <#kw>#elseif <#id>CONF +#elseif CONF + // CHECK: var y3 : String + var y3 : String +// CHECK: <#kw>#else +#else + // CHECK: var y3 : Float + var y3 : Float +// CHECK: <#kw>#endif +#endif + +// CHECK: var l : Int +var l : Int + +// CHECK: class C1 { +class C1 { + // CHECK: var f : Int + var f : Int + +// CHECK: <#kw>#if <#id>CONF +#if CONF + // CHECK: var x : Int + var x : Int +// CHECK: <#kw>#else +#else + // CHECK: var x : Float + var x : Float +// CHECK: <#kw>#endif +#endif + +// CHECK: <#kw>#if <#id>CONF +#if CONF + // CHECK: var x2 : Int + var x2 : Int +// CHECK: <#kw>#endif +#endif + +// CHECK: <#kw>#if !<#id>CONF +#if !CONF + // CHECK: var x3 : Int + var x3 : Int +// CHECK: <#kw>#else +#else + // CHECK: var x3 : Float + var x3 : Float +// CHECK: <#kw>#endif +#endif + +// CHECK: <#kw>#if !<#id>CONF +#if !CONF + // CHECK: var x4 : Int + var x4 : Int +// CHECK: <#kw>#endif +#endif + +// CHECK: <#kw>#if <#id>CONF +#if CONF + // CHECK: var y1 : Int + var y1 : Int +// CHECK: <#kw>#elseif <#id>BAZ +#elseif BAZ + // CHECK: var y1 : String + var y1 : String +// CHECK: <#kw>#else +#else + // CHECK: var y1 : Float + var y1 : Float +// CHECK: <#kw>#endif +#endif + +// CHECK: <#kw>#if !<#id>CONF +#if !CONF + // CHECK: var y2 : Int + var y2 : Int +// CHECK: <#kw>#elseif <#id>BAZ +#elseif BAZ + // CHECK: var y2 : String + var y2 : String +// CHECK: <#kw>#else +#else + // CHECK: var y2 : Float + var y2 : Float +// CHECK: <#kw>#endif +#endif + +// CHECK: <#kw>#if !<#id>CONF +#if !CONF + // CHECK: var y3 : Int + var y3 : Int +// CHECK: <#kw>#elseif <#id>CONF +#elseif CONF + // CHECK: var y3 : String + var y3 : String +// CHECK: <#kw>#else +#else + // CHECK: var y3 : Float + var y3 : Float +// CHECK: <#kw>#endif +#endif + + // CHECK: var l : Int + var l : Int +} + +// CHECK: func test1() { +func test1() { + // CHECK: var f : Int + var f : Int + +// CHECK: <#kw>#if <#id>CONF +#if CONF + // CHECK: var x : Int + var x : Int +// CHECK: <#kw>#else +#else + // CHECK: var x : Float + var x : Float +// CHECK: <#kw>#endif +#endif + +// CHECK: <#kw>#if <#id>CONF +#if CONF + // CHECK: var x2 : Int + var x2 : Int +// CHECK: <#kw>#endif +#endif + +// CHECK: <#kw>#if !<#id>CONF +#if !CONF + // CHECK: var x3 : Int + var x3 : Int +// CHECK: <#kw>#else +#else + // CHECK: var x3 : Float + var x3 : Float +// CHECK: <#kw>#endif +#endif + +// CHECK: <#kw>#if !<#id>CONF +#if !CONF + // CHECK: var x4 : Int + var x4 : Int +// CHECK: <#kw>#endif +#endif + +// CHECK: <#kw>#if <#id>CONF +#if CONF + // CHECK: var y1 : Int + var y1 : Int +// CHECK: <#kw>#elseif <#id>BAZ +#elseif BAZ + // CHECK: var y1 : String + var y1 : String +// CHECK: <#kw>#else +#else + // CHECK: var y1 : Float + var y1 : Float +// CHECK: <#kw>#endif +#endif + +// CHECK: <#kw>#if !<#id>CONF +#if !CONF + // CHECK: var y2 : Int + var y2 : Int +// CHECK: <#kw>#elseif <#id>BAZ +#elseif BAZ + // CHECK: var y2 : String + var y2 : String +// CHECK: <#kw>#else +#else + // CHECK: var y2 : Float + var y2 : Float +// CHECK: <#kw>#endif +#endif + +// CHECK: <#kw>#if !<#id>CONF +#if !CONF + // CHECK: var y3 : Int + var y3 : Int +// CHECK: <#kw>#elseif <#id>CONF +#elseif CONF + // CHECK: var y3 : String + var y3 : String +// CHECK: <#kw>#else +#else + // CHECK: var y3 : Float + var y3 : Float +// CHECK: <#kw>#endif +#endif + + // CHECK: var l : Int + var l : Int +} + +// CHECK: class C2 { +class C2 { + // CHECK: <#kw>#if <#id>os(<#id>iOS) + #if os(iOS) + // CHECK: func foo() {} + func foo() {} + #endif +} + +class NestedPoundIf { +// CHECK: class NestedPoundIf { + func foo1() { +// CHECK: func foo1() { + #if os(macOS) +// CHECK: <#kw>#if <#id>os(<#id>macOS) + var a = 1 +// CHECK: var a = 1 + #if USE_METAL +// CHECK: <#kw>#if <#id>USE_METAL + var b = 2 +// CHECK: var b = 2 + #if os(iOS) +// CHECK: <#kw>#if <#id>os(<#id>iOS) + var c = 3 +// CHECK: var c = 3 + #else +// CHECK: <#kw>#else + var c = 3 +// CHECK: var c = 3 + #endif +// CHECK: <#kw>#endif + #else +// CHECK: <#kw>#else + var b = 2 +// CHECK: var b = 2 + #endif +// CHECK: <#kw>#endif + #else +// CHECK: <#kw>#else + var a = 1 +// CHECK: var a = 1 + #endif +// CHECK: <#kw>#endif + } + func foo2() {} +// CHECK: func foo2() {} + func foo3() {} +// CHECK: func foo3() {} +} + +// CHECK: <#kw>#error("Error") +#error("Error") +// CHECK: <#kw>#warning("Warning") +#warning("Warning") +// CHECK: <#kw>#sourceLocation(file: "x", line: 1) +#sourceLocation(file: "x", line: 1) +// CHECK: #line 17 "abc.swift" +#line 17 "abc.swift" + +@available(iOS 8.0, OSX 10.10, *) +// CHECK: @available(iOS 8.0, OSX 10.10, *) +func foo() { +// CHECK: if #available (OSX 10.10, iOS 8.01, *) {let _ = "iOS"} + if #available (OSX 10.10, iOS 8.01, *) {let _ = "iOS"} +} + +// CHECK: func test4(inout a: Int) {{{$}} +func test4(inout a: Int) { + // CHECK-OLD: if #available (OSX >= 10.10, iOS >= 8.01) {let OSX = "iOS"}}{{$}} + // CHECK-NEW: if #available (OSX >= 10.10, iOS >= 8.01) {let OSX = "iOS"}}{{$}} + if #available (OSX >= 10.10, iOS >= 8.01) {let OSX = "iOS"}} + +// CHECK: func test4b(a: inout Int) {{{$}} +func test4b(a: inout Int) { +} + +let filename = #file +// CHECK: let filename = #file +let line = #line +// CHECK: let line = #line +let column = #column +// CHECK: let column = #column +let function = #function +// CHECK: let function = #function + +let image = #imageLiteral(resourceName: "cloud.png") +// CHECK-OLD: let image = #imageLiteral(resourceName: "cloud.png") +// CHECK-NEW: let image = #imageLiteral(resourceName: "cloud.png") +let file = #fileLiteral(resourceName: "cloud.png") +// CHECK-OLD: let file = #fileLiteral(resourceName: "cloud.png") +// CHECK-NEW: let file = #fileLiteral(resourceName: "cloud.png") +let black = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1) +// CHECK-OLD: let black = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1) +// CHECK-NEW: let black = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1) + +let rgb = [#colorLiteral(red: 1, green: 0, blue: 0, alpha: 1), + #colorLiteral(red: 0, green: 1, blue: 0, alpha: 1), + #colorLiteral(red: 0, green: 0, blue: 1, alpha: 1)] +// CHECK-OLD: let rgb = [#colorLiteral(red: 1, green: 0, blue: 0, alpha: 1), +// CHECK-OLD: #colorLiteral(red: 0, green: 1, blue: 0, alpha: 1), +// CHECK-OLD: #colorLiteral(red: 0, green: 0, blue: 1, alpha: 1)] +// CHECK-NEW: let rgb = [#colorLiteral(red: 1, green: 0, blue: 0, alpha: 1), +// CHECK-NEW: #colorLiteral(red: 0, green: 1, blue: 0, alpha: 1), +// CHECK-NEW: #colorLiteral(red: 0, green: 0, blue: 1, alpha: 1)] diff --git a/lit_tests/coloring_keywords.swift b/lit_tests/coloring_keywords.swift new file mode 100644 index 00000000000..3c7c8d4c689 --- /dev/null +++ b/lit_tests/coloring_keywords.swift @@ -0,0 +1,11 @@ +// RUN: %lit-test-helper classify-syntax -source-file %s | %FileCheck %s + +// CHECK: return c.return + +class C { + var `return` = 2 +} + +func foo(_ c: C) -> Int { + return c.return +} diff --git a/lit_tests/coloring_unclosed_hash_if.swift b/lit_tests/coloring_unclosed_hash_if.swift new file mode 100644 index 00000000000..268fcea9348 --- /dev/null +++ b/lit_tests/coloring_unclosed_hash_if.swift @@ -0,0 +1,13 @@ +// RUN: %lit-test-helper classify-syntax -source-file %s | %FileCheck %s + +// CHECK: <#kw>#if <#id>d +// CHECK-NEXT: func bar() { +// CHECK-NEXT: <#kw>#if <#id>d +// CHECK-NEXT: } +// CHECK-NEXT: func foo() {} + +#if d +func bar() { + #if d +} +func foo() {} diff --git a/lit_tests/lit.cfg b/lit_tests/lit.cfg new file mode 100644 index 00000000000..c44a84613b4 --- /dev/null +++ b/lit_tests/lit.cfg @@ -0,0 +1,63 @@ +# ------- test/lit.cfg - Configuration for the 'lit' test runner -*- python -*- +# +# This source file is part of the Swift.org open source project +# +# Copyright (c) 2014 - 2018 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 lit +import tempfile + +def inferSwiftBinaryImpl(binaryName, envVarName): + # If the user set the variable in the environment, definitely use that. + execPath = os.getenv(envVarName) + if execPath: + return execPath + + # Otherwise, see if we were passed the path to the binary using a param. + execPath = lit_config.params.get(envVarName) + if execPath: + return execPath + + # Lastly, look in the path. + return lit.util.which(binaryName, config.environment['PATH']) + + +# Discover the Swift binaries to use. +def inferSwiftBinary(binaryName): + envVarName = binaryName.upper().replace("-", "_") + execPath = inferSwiftBinaryImpl(binaryName, envVarName) + + if execPath: + if not lit_config.quiet: + lit_config.note('using %s: %s' % (binaryName, execPath)) + else: + msg = "couldn't find '%s' program, try setting %s in your environment" + lit_config.warning(msg % (binaryName, envVarName)) + + # Just substitute the plain executable name, so the run line remains + # reasonable. + execPath = binaryName + + return execPath + +config.name = 'SwiftSyntax' +config.suffixes = ['.swift'] +config.test_format = lit.formats.ShTest(execute_external=True) +config.test_exec_root = tempfile.gettempdir() + +config.filecheck = inferSwiftBinary('FileCheck') +config.swift_syntax_test = inferSwiftBinary('swift-syntax-test') +config.lit_test_helper = inferSwiftBinary('lit-test-helper') +config.incr_transfer_round_trip = inferSwiftBinary('incr_transfer_round_trip.py') + +config.substitutions.append(('%empty-directory\(([^)]+)\)', 'rm -rf \"\\1\" && mkdir -p \"\\1\"')) +config.substitutions.append(('%FileCheck', config.filecheck)) +config.substitutions.append(('%incr-transfer-roundtrip', '%s --temp-dir %%t --swift-syntax-test %%swift-syntax-test --swift-swiftsyntax-test %%lit-test-helper' % (config.incr_transfer_round_trip))) +config.substitutions.append(('%swift-syntax-test', config.swift_syntax_test)) +config.substitutions.append(('%lit-test-helper', config.lit_test_helper)) diff --git a/lit_tests/simple.swift b/lit_tests/simple.swift new file mode 100644 index 00000000000..9692df76559 --- /dev/null +++ b/lit_tests/simple.swift @@ -0,0 +1,49 @@ +// RUN: %empty-directory(%t) +// RUN: %incr-transfer-roundtrip %s --test-case REPLACE +// RUN: %incr-transfer-roundtrip %s --test-case REPLACE --serialization-format byteTree +// RUN: %incr-transfer-roundtrip %s --test-case INSERT +// RUN: %incr-transfer-roundtrip %s --test-case REMOVE +// RUN: %incr-transfer-roundtrip %s --test-case CLASS_SURROUNDING +// RUN: %incr-transfer-roundtrip %s --test-case MULTI_EDIT +// RUN: %incr-transfer-roundtrip %s --test-case REPLACE_WITH_MULTI_BYTE_CHAR +// RUN: %incr-transfer-roundtrip %s --test-case REPLACE_WITH_MULTI_BYTE_CHAR --serialization-format byteTree + +func start() {} + + +func foo() { +} + +_ = <>> +_ = <>> +_ = <>> +<>> +<>> +foo() +<>> +_ = 1 + +<>> + func method1() {} + + <>> + func method1() {} + <>> + + <>> = <>> + + let x = "<>>" + let x = "<>>" + + private struc<>> MyStruct { + } + + var computedVar: [Int] { + return [1 + <>> + } + + if true <>> + _ = 5 +} +