From 605a5b8847eec8b18710afef0c48781c51bf1aa0 Mon Sep 17 00:00:00 2001 From: Ziyang Huang Date: Thu, 10 Aug 2023 22:40:12 +0800 Subject: [PATCH] Parse shebang as a child of `SourceFileSyntax` --- .../Sources/SyntaxSupport/DeclNodes.swift | 6 ++ .../Sources/SyntaxSupport/TokenSpec.swift | 3 + .../Sources/SyntaxSupport/Trivia.swift | 5 -- .../SwiftIDEUtils/SyntaxClassification.swift | 2 + Sources/SwiftParser/Lexer/Cursor.swift | 12 ++-- .../SwiftParser/Lexer/RegexLiteralLexer.swift | 4 ++ Sources/SwiftParser/TokenPrecedence.swift | 4 +- Sources/SwiftParser/TopLevel.swift | 2 + Sources/SwiftParser/TriviaParser.swift | 9 --- .../generated/TokenSpecStaticMembers.swift | 4 ++ .../generated/TokenNameForDiagnostics.swift | 2 + Sources/SwiftSyntax/SourceLocation.swift | 3 +- Sources/SwiftSyntax/Trivia.swift | 2 +- .../generated/ChildNameForKeyPath.swift | 8 ++- .../RenamedChildrenCompatibility.swift | 10 +++- Sources/SwiftSyntax/generated/TokenKind.swift | 14 +++++ Sources/SwiftSyntax/generated/Tokens.swift | 16 +++++ .../SwiftSyntax/generated/TriviaPieces.swift | 22 ------- .../generated/raw/RawSyntaxNodes.swift | 36 +++++++---- .../generated/raw/RawSyntaxValidation.swift | 8 ++- .../generated/syntaxNodes/SyntaxNodes.swift | 59 ++++++++++++++----- .../generated/BuildableNodes.swift | 8 ++- .../RenamedChildrenBuilderCompatibility.swift | 8 ++- .../MacroSystem.swift | 2 +- Tests/SwiftParserTest/LexerTests.swift | 3 +- Tests/SwiftParserTest/TriviaParserTests.swift | 15 ----- .../translated/HashbangLibraryTests.swift | 4 +- 27 files changed, 166 insertions(+), 105 deletions(-) diff --git a/CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift b/CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift index 2166df21eea..2b5dd270d96 100644 --- a/CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift +++ b/CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift @@ -1997,6 +1997,12 @@ public let DECL_NODES: [Node] = [ parserFunction: "parseSourceFile", traits: ["WithStatements"], children: [ + Child( + name: "Shebang", + kind: .token(choices: [.token(.shebang)]), + documentation: "A shebang can specify the path of the compiler when using Swift source file as a script.", + isOptional: true + ), Child( name: "Statements", kind: .collection(kind: .codeBlockItemList, collectionElementName: "Statement") diff --git a/CodeGeneration/Sources/SyntaxSupport/TokenSpec.swift b/CodeGeneration/Sources/SyntaxSupport/TokenSpec.swift index fe59d084c42..bc0749b7aee 100644 --- a/CodeGeneration/Sources/SyntaxSupport/TokenSpec.swift +++ b/CodeGeneration/Sources/SyntaxSupport/TokenSpec.swift @@ -113,6 +113,7 @@ public enum Token: CaseIterable { case rightParen case rightSquare case semicolon + case shebang case singleQuote case stringQuote case stringSegment @@ -209,6 +210,8 @@ public enum Token: CaseIterable { return .punctuator(name: "rightSquare", text: "]") case .semicolon: return .punctuator(name: "semicolon", text: ";") + case .shebang: + return .other(name: "shebang", nameForDiagnostics: "shebang") case .singleQuote: return .punctuator(name: "singleQuote", text: "\'") case .stringQuote: diff --git a/CodeGeneration/Sources/SyntaxSupport/Trivia.swift b/CodeGeneration/Sources/SyntaxSupport/Trivia.swift index dee7b5a8c4e..35699667188 100644 --- a/CodeGeneration/Sources/SyntaxSupport/Trivia.swift +++ b/CodeGeneration/Sources/SyntaxSupport/Trivia.swift @@ -159,11 +159,6 @@ public let TRIVIAS: [Trivia] = [ ] ), - Trivia( - name: "Shebang", - comment: #"A script command, starting with '#!'."# - ), - Trivia( name: "Space", comment: #"A space ' ' character."#, diff --git a/Sources/SwiftIDEUtils/SyntaxClassification.swift b/Sources/SwiftIDEUtils/SyntaxClassification.swift index 5ba88f8b523..e33feca127a 100644 --- a/Sources/SwiftIDEUtils/SyntaxClassification.swift +++ b/Sources/SwiftIDEUtils/SyntaxClassification.swift @@ -193,6 +193,8 @@ extension RawTokenKind { return .none case .semicolon: return .none + case .shebang: + return .none case .singleQuote: return .stringLiteral case .stringQuote: diff --git a/Sources/SwiftParser/Lexer/Cursor.swift b/Sources/SwiftParser/Lexer/Cursor.swift index b7118fb9031..57deef40b1d 100644 --- a/Sources/SwiftParser/Lexer/Cursor.swift +++ b/Sources/SwiftParser/Lexer/Cursor.swift @@ -905,6 +905,11 @@ extension Lexer.Cursor { case UInt8(ascii: "\\"): _ = self.advance(); return Lexer.Result(.backslash) case UInt8(ascii: "#"): + // Try lex shebang. + if self.isAtStartOfFile, self.peek(at: 1) == UInt8(ascii: "!") { + self.advanceToEndOfLine() + return Lexer.Result(.shebang) + } // Try lex a raw string literal. if let delimiterLength = self.advanceIfOpeningRawStringDelimiter() { return Lexer.Result(.rawStringPoundDelimiter, stateTransition: .push(newState: .afterRawStringDelimiter(delimiterLength: delimiterLength))) @@ -1171,12 +1176,6 @@ extension Lexer.Cursor { default: break } - case UInt8(ascii: "#"): - guard start.isAtStartOfFile, self.advance(if: { $0 == "!" }) else { - break - } - self.advanceToEndOfLine() - continue case UInt8(ascii: "<"), UInt8(ascii: ">"): if self.tryLexConflictMarker(start: start) { error = LexingDiagnostic(.sourceConflictMarker, position: start) @@ -1189,6 +1188,7 @@ extension Lexer.Cursor { UInt8(ascii: "}"), UInt8(ascii: "]"), UInt8(ascii: ")"), UInt8(ascii: "@"), UInt8(ascii: ","), UInt8(ascii: ";"), UInt8(ascii: ":"), UInt8(ascii: "\\"), UInt8(ascii: "$"), + UInt8(ascii: "#"), // Start of integer/hex/float literals. UInt8(ascii: "0"), UInt8(ascii: "1"), UInt8(ascii: "2"), diff --git a/Sources/SwiftParser/Lexer/RegexLiteralLexer.swift b/Sources/SwiftParser/Lexer/RegexLiteralLexer.swift index 300ceda63ca..af6094a78ae 100644 --- a/Sources/SwiftParser/Lexer/RegexLiteralLexer.swift +++ b/Sources/SwiftParser/Lexer/RegexLiteralLexer.swift @@ -625,6 +625,10 @@ extension Lexer.Cursor { case .arrow, .ellipsis, .period, .atSign, .pound, .backtick, .backslash: return false + // Shebang does not sequence expressions. + case .shebang: + return false + case .keyword: // There are a handful of keywords that are expressions, handle them. // Otherwise, a regex literal can generally be parsed after a keyword. diff --git a/Sources/SwiftParser/TokenPrecedence.swift b/Sources/SwiftParser/TokenPrecedence.swift index 3eb38c2802f..5657b6fde19 100644 --- a/Sources/SwiftParser/TokenPrecedence.swift +++ b/Sources/SwiftParser/TokenPrecedence.swift @@ -124,8 +124,8 @@ enum TokenPrecedence: Comparable { .dollarIdentifier, .identifier, // '_' can occur in types to replace a type identifier .wildcard, - // String segment, string interpolation anchor, pound, and regex pattern don't really fit anywhere else - .pound, .stringSegment, .regexLiteralPattern: + // String segment, string interpolation anchor, pound, shebang and regex pattern don't really fit anywhere else + .pound, .stringSegment, .regexLiteralPattern, .shebang: self = .identifierLike // MARK: Expr keyword diff --git a/Sources/SwiftParser/TopLevel.swift b/Sources/SwiftParser/TopLevel.swift index 55c8a112879..3dee2442501 100644 --- a/Sources/SwiftParser/TopLevel.swift +++ b/Sources/SwiftParser/TopLevel.swift @@ -40,10 +40,12 @@ extension Parser { /// ``Parser/parse(source:parseTransition:filenameForDiagnostics:languageVersion:enableBareSlashRegexLiteral:)-7tndx`` /// API calls. mutating func parseSourceFile() -> RawSourceFileSyntax { + let shebang = self.consume(if: .shebang) let items = self.parseTopLevelCodeBlockItems() let unexpectedBeforeEndOfFileToken = consumeRemainingTokens() let endOfFile = self.consume(if: .endOfFile)! return .init( + shebang: shebang, statements: items, RawUnexpectedNodesSyntax(unexpectedBeforeEndOfFileToken, arena: self.arena), endOfFileToken: endOfFile, diff --git a/Sources/SwiftParser/TriviaParser.swift b/Sources/SwiftParser/TriviaParser.swift index 1be9ea9211c..a930645f426 100644 --- a/Sources/SwiftParser/TriviaParser.swift +++ b/Sources/SwiftParser/TriviaParser.swift @@ -81,15 +81,6 @@ public struct TriviaParser { } case UInt8(ascii: "#"): - // "#!...": .shebang - // NOTE: .shebang appears only if this trivia is at the start of the - // file. We don't know if this trivia is at the start of the file, but - // we believe that the lexer lexed it accordingly. - if position == .leading && pieces.isEmpty && cursor.advance(if: { $0 == "!" }) { - cursor.advanceToEndOfLine() - pieces.append(.shebang(start.text(upTo: cursor))) - continue - } cursor.advance(while: { $0 == "#" }) pieces.append(.pounds(start.distance(to: cursor))) continue diff --git a/Sources/SwiftParser/generated/TokenSpecStaticMembers.swift b/Sources/SwiftParser/generated/TokenSpecStaticMembers.swift index 4b994dd023b..6df8195bd1a 100644 --- a/Sources/SwiftParser/generated/TokenSpecStaticMembers.swift +++ b/Sources/SwiftParser/generated/TokenSpecStaticMembers.swift @@ -187,6 +187,10 @@ extension TokenSpec { return TokenSpec(.semicolon) } + static var shebang: TokenSpec { + return TokenSpec(.shebang) + } + static var singleQuote: TokenSpec { return TokenSpec(.singleQuote) } diff --git a/Sources/SwiftParserDiagnostics/generated/TokenNameForDiagnostics.swift b/Sources/SwiftParserDiagnostics/generated/TokenNameForDiagnostics.swift index 8e069bea2a6..08e7f6ffde7 100644 --- a/Sources/SwiftParserDiagnostics/generated/TokenNameForDiagnostics.swift +++ b/Sources/SwiftParserDiagnostics/generated/TokenNameForDiagnostics.swift @@ -103,6 +103,8 @@ extension TokenKind { return "]" case .semicolon: return ";" + case .shebang: + return "shebang" case .singleQuote: return "'" case .stringQuote: diff --git a/Sources/SwiftSyntax/SourceLocation.swift b/Sources/SwiftSyntax/SourceLocation.swift index a32d878ace0..b57bb10a3da 100644 --- a/Sources/SwiftSyntax/SourceLocation.swift +++ b/Sources/SwiftSyntax/SourceLocation.swift @@ -616,8 +616,7 @@ fileprivate extension RawTriviaPiece { body(carriageReturnLineLength) } lineLength = .zero - case let .shebang(text), - let .lineComment(text), + case let .lineComment(text), let .docLineComment(text): // Line comments are not supposed to contain newlines. precondition(!text.containsSwiftNewline(), "line comment created that contained a new-line character") diff --git a/Sources/SwiftSyntax/Trivia.swift b/Sources/SwiftSyntax/Trivia.swift index e9db67151ea..2cf9b27ce58 100644 --- a/Sources/SwiftSyntax/Trivia.swift +++ b/Sources/SwiftSyntax/Trivia.swift @@ -192,7 +192,7 @@ extension Trivia { return Array(repeating: TriviaPiece.carriageReturns(1), count: count) case .carriageReturnLineFeeds(let count): return Array(repeating: TriviaPiece.carriageReturnLineFeeds(1), count: count) - case .lineComment, .blockComment, .docLineComment, .docBlockComment, .unexpectedText, .shebang: + case .lineComment, .blockComment, .docLineComment, .docBlockComment, .unexpectedText: return [piece] } }) diff --git a/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift b/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift index 8e0c72c6c71..08ab022d77b 100644 --- a/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift +++ b/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift @@ -2779,8 +2779,12 @@ public func childName(_ keyPath: AnyKeyPath) -> String? { return "constraint" case \SomeOrAnyTypeSyntax.unexpectedAfterConstraint: return "unexpectedAfterConstraint" - case \SourceFileSyntax.unexpectedBeforeStatements: - return "unexpectedBeforeStatements" + case \SourceFileSyntax.unexpectedBeforeShebang: + return "unexpectedBeforeShebang" + case \SourceFileSyntax.shebang: + return "shebang" + case \SourceFileSyntax.unexpectedBetweenShebangAndStatements: + return "unexpectedBetweenShebangAndStatements" case \SourceFileSyntax.statements: return "statements" case \SourceFileSyntax.unexpectedBetweenStatementsAndEndOfFileToken: diff --git a/Sources/SwiftSyntax/generated/RenamedChildrenCompatibility.swift b/Sources/SwiftSyntax/generated/RenamedChildrenCompatibility.swift index 0c9a58e155f..7369edd104b 100644 --- a/Sources/SwiftSyntax/generated/RenamedChildrenCompatibility.swift +++ b/Sources/SwiftSyntax/generated/RenamedChildrenCompatibility.swift @@ -7056,11 +7056,13 @@ extension SourceFileSyntax { } } - @available(*, deprecated, renamed: "SourceFileSyntax(leadingTrivia:_:statements:_:endOfFileToken:_:trailingTrivia:)") + @available(*, deprecated, renamed: "SourceFileSyntax(leadingTrivia:_:shebang:_:statements:_:endOfFileToken:_:trailingTrivia:)") @_disfavoredOverload public init( leadingTrivia: Trivia? = nil, - _ unexpectedBeforeStatements: UnexpectedNodesSyntax? = nil, + _ unexpectedBeforeShebang: UnexpectedNodesSyntax? = nil, + shebang: TokenSyntax? = nil, + _ unexpectedBetweenShebangAndStatements: UnexpectedNodesSyntax? = nil, statements: CodeBlockItemListSyntax, _ unexpectedBetweenStatementsAndEOFToken: UnexpectedNodesSyntax? = nil, eofToken: TokenSyntax = .endOfFileToken(), @@ -7070,7 +7072,9 @@ extension SourceFileSyntax { ) { self.init( leadingTrivia: leadingTrivia, - unexpectedBeforeStatements, + unexpectedBeforeShebang, + shebang: shebang, + unexpectedBetweenShebangAndStatements, statements: statements, unexpectedBetweenStatementsAndEOFToken, endOfFileToken: eofToken, diff --git a/Sources/SwiftSyntax/generated/TokenKind.swift b/Sources/SwiftSyntax/generated/TokenKind.swift index 746df258be5..aab0b5d5fd9 100644 --- a/Sources/SwiftSyntax/generated/TokenKind.swift +++ b/Sources/SwiftSyntax/generated/TokenKind.swift @@ -58,6 +58,7 @@ public enum TokenKind: Hashable { case rightParen case rightSquare case semicolon + case shebang(String) case singleQuote case stringQuote case stringSegment(String) @@ -156,6 +157,8 @@ public enum TokenKind: Hashable { return #"]"# case .semicolon: return #";"# + case .shebang(let text): + return text case .singleQuote: return #"'"# case .stringQuote: @@ -347,6 +350,8 @@ public enum TokenKind: Hashable { return true case .semicolon: return true + case .shebang: + return false case .singleQuote: return true case .stringQuote: @@ -452,6 +457,8 @@ extension TokenKind: Equatable { return true case (.semicolon, .semicolon): return true + case (.shebang(let lhsText), .shebang(let rhsText)): + return lhsText == rhsText case (.singleQuote, .singleQuote): return true case (.stringQuote, .stringQuote): @@ -519,6 +526,7 @@ public enum RawTokenKind: UInt8, Equatable, Hashable { case rightParen case rightSquare case semicolon + case shebang case singleQuote case stringQuote case stringSegment @@ -700,6 +708,8 @@ public enum RawTokenKind: UInt8, Equatable, Hashable { return true case .semicolon: return true + case .shebang: + return false case .singleQuote: return true case .stringQuote: @@ -843,6 +853,8 @@ extension TokenKind { case .semicolon: precondition(text.isEmpty || rawKind.defaultText.map(String.init) == text) return .semicolon + case .shebang: + return .shebang(text) case .singleQuote: precondition(text.isEmpty || rawKind.defaultText.map(String.init) == text) return .singleQuote @@ -952,6 +964,8 @@ extension TokenKind { return (.rightSquare, nil) case .semicolon: return (.semicolon, nil) + case .shebang(let str): + return (.shebang, str) case .singleQuote: return (.singleQuote, nil) case .stringQuote: diff --git a/Sources/SwiftSyntax/generated/Tokens.swift b/Sources/SwiftSyntax/generated/Tokens.swift index ce26945133b..35d0e19ea09 100644 --- a/Sources/SwiftSyntax/generated/Tokens.swift +++ b/Sources/SwiftSyntax/generated/Tokens.swift @@ -651,6 +651,22 @@ extension TokenSyntax { ) } + public static func shebang( + _ text: String, + leadingTrivia: Trivia = [], + trailingTrivia: Trivia = [], + presence: SourcePresence = .present + + ) -> TokenSyntax { + return TokenSyntax( + .shebang(text), + leadingTrivia: leadingTrivia, + trailingTrivia: trailingTrivia, + presence: presence + + ) + } + public static func singleQuoteToken( leadingTrivia: Trivia = [], trailingTrivia: Trivia = [], diff --git a/Sources/SwiftSyntax/generated/TriviaPieces.swift b/Sources/SwiftSyntax/generated/TriviaPieces.swift index 8f9974a29e7..9e8e4ec4bfb 100644 --- a/Sources/SwiftSyntax/generated/TriviaPieces.swift +++ b/Sources/SwiftSyntax/generated/TriviaPieces.swift @@ -41,8 +41,6 @@ public enum TriviaPiece { case newlines(Int) /// A '#' that is at the end of a line in a multi-line string literal to escape the newline. case pounds(Int) - /// A script command, starting with '#!'. - case shebang(String) /// A space ' ' character. case spaces(Int) /// A tab '\t' character. @@ -84,8 +82,6 @@ extension TriviaPiece: TextOutputStreamable { printRepeated("\n", count: count) case let .pounds(count): printRepeated("#", count: count) - case let .shebang(text): - target.write(text) case let .spaces(count): printRepeated(" ", count: count) case let .tabs(count): @@ -122,8 +118,6 @@ extension TriviaPiece: CustomDebugStringConvertible { return "newlines(\(data))" case .pounds(let data): return "pounds(\(data))" - case .shebang(let name): - return "shebang(\(name.debugDescription))" case .spaces(let data): return "spaces(\(data))" case .tabs(let data): @@ -217,11 +211,6 @@ extension Trivia { return .pounds(1) } - /// Returns a piece of trivia for Shebang. - public static func shebang(_ text: String) -> Trivia { - return [.shebang(text)] - } - /// Returns a piece of trivia for some number of " " characters. public static func spaces(_ count: Int) -> Trivia { return [.spaces(count)] @@ -283,8 +272,6 @@ extension TriviaPiece { return SourceLength(utf8Length: count) case let .pounds(count): return SourceLength(utf8Length: count) - case let .shebang(text): - return SourceLength(of: text) case let .spaces(count): return SourceLength(utf8Length: count) case let .tabs(count): @@ -313,7 +300,6 @@ public enum RawTriviaPiece: Equatable { case lineComment(SyntaxText) case newlines(Int) case pounds(Int) - case shebang(SyntaxText) case spaces(Int) case tabs(Int) case unexpectedText(SyntaxText) @@ -341,8 +327,6 @@ public enum RawTriviaPiece: Equatable { return .newlines(count) case let .pounds(count): return .pounds(count) - case let .shebang(text): - return .shebang(arena.intern(text)) case let .spaces(count): return .spaces(count) case let .tabs(count): @@ -378,8 +362,6 @@ extension TriviaPiece { self = .newlines(count) case let .pounds(count): self = .pounds(count) - case let .shebang(text): - self = .shebang(String(syntaxText: text)) case let .spaces(count): self = .spaces(count) case let .tabs(count): @@ -415,8 +397,6 @@ extension RawTriviaPiece { return count case let .pounds(count): return count - case let .shebang(text): - return text.count case let .spaces(count): return count case let .tabs(count): @@ -450,8 +430,6 @@ extension RawTriviaPiece { return nil case .pounds(_): return nil - case .shebang(let text): - return text case .spaces(_): return nil case .tabs(_): diff --git a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodes.swift b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodes.swift index 85fa79f4b60..c1eb2907fa2 100644 --- a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodes.swift +++ b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodes.swift @@ -18406,7 +18406,9 @@ public struct RawSourceFileSyntax: RawSyntaxNodeProtocol { } public init( - _ unexpectedBeforeStatements: RawUnexpectedNodesSyntax? = nil, + _ unexpectedBeforeShebang: RawUnexpectedNodesSyntax? = nil, + shebang: RawTokenSyntax?, + _ unexpectedBetweenShebangAndStatements: RawUnexpectedNodesSyntax? = nil, statements: RawCodeBlockItemListSyntax, _ unexpectedBetweenStatementsAndEndOfFileToken: RawUnexpectedNodesSyntax? = nil, endOfFileToken: RawTokenSyntax, @@ -18414,35 +18416,45 @@ public struct RawSourceFileSyntax: RawSyntaxNodeProtocol { arena: __shared SyntaxArena ) { let raw = RawSyntax.makeLayout( - kind: .sourceFile, uninitializedCount: 5, arena: arena) { layout in + kind: .sourceFile, uninitializedCount: 7, arena: arena) { layout in layout.initialize(repeating: nil) - layout[0] = unexpectedBeforeStatements?.raw - layout[1] = statements.raw - layout[2] = unexpectedBetweenStatementsAndEndOfFileToken?.raw - layout[3] = endOfFileToken.raw - layout[4] = unexpectedAfterEndOfFileToken?.raw + layout[0] = unexpectedBeforeShebang?.raw + layout[1] = shebang?.raw + layout[2] = unexpectedBetweenShebangAndStatements?.raw + layout[3] = statements.raw + layout[4] = unexpectedBetweenStatementsAndEndOfFileToken?.raw + layout[5] = endOfFileToken.raw + layout[6] = unexpectedAfterEndOfFileToken?.raw } self.init(unchecked: raw) } - public var unexpectedBeforeStatements: RawUnexpectedNodesSyntax? { + public var unexpectedBeforeShebang: RawUnexpectedNodesSyntax? { layoutView.children[0].map(RawUnexpectedNodesSyntax.init(raw:)) } + public var shebang: RawTokenSyntax? { + layoutView.children[1].map(RawTokenSyntax.init(raw:)) + } + + public var unexpectedBetweenShebangAndStatements: RawUnexpectedNodesSyntax? { + layoutView.children[2].map(RawUnexpectedNodesSyntax.init(raw:)) + } + public var statements: RawCodeBlockItemListSyntax { - layoutView.children[1].map(RawCodeBlockItemListSyntax.init(raw:))! + layoutView.children[3].map(RawCodeBlockItemListSyntax.init(raw:))! } public var unexpectedBetweenStatementsAndEndOfFileToken: RawUnexpectedNodesSyntax? { - layoutView.children[2].map(RawUnexpectedNodesSyntax.init(raw:)) + layoutView.children[4].map(RawUnexpectedNodesSyntax.init(raw:)) } public var endOfFileToken: RawTokenSyntax { - layoutView.children[3].map(RawTokenSyntax.init(raw:))! + layoutView.children[5].map(RawTokenSyntax.init(raw:))! } public var unexpectedAfterEndOfFileToken: RawUnexpectedNodesSyntax? { - layoutView.children[4].map(RawUnexpectedNodesSyntax.init(raw:)) + layoutView.children[6].map(RawUnexpectedNodesSyntax.init(raw:)) } } diff --git a/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift b/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift index d017a2a5822..2b91f7f3fa7 100644 --- a/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift +++ b/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift @@ -2242,12 +2242,14 @@ func validateLayout(layout: RawSyntaxBuffer, as kind: SyntaxKind) { assertNoError(kind, 3, verify(layout[3], as: RawTypeSyntax.self)) assertNoError(kind, 4, verify(layout[4], as: RawUnexpectedNodesSyntax?.self)) case .sourceFile: - assert(layout.count == 5) + assert(layout.count == 7) assertNoError(kind, 0, verify(layout[0], as: RawUnexpectedNodesSyntax?.self)) - assertNoError(kind, 1, verify(layout[1], as: RawCodeBlockItemListSyntax.self)) + assertNoError(kind, 1, verify(layout[1], as: RawTokenSyntax?.self, tokenChoices: [.tokenKind(.shebang)])) assertNoError(kind, 2, verify(layout[2], as: RawUnexpectedNodesSyntax?.self)) - assertNoError(kind, 3, verify(layout[3], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.endOfFile)])) + assertNoError(kind, 3, verify(layout[3], as: RawCodeBlockItemListSyntax.self)) assertNoError(kind, 4, verify(layout[4], as: RawUnexpectedNodesSyntax?.self)) + assertNoError(kind, 5, verify(layout[5], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.endOfFile)])) + assertNoError(kind, 6, verify(layout[6], as: RawUnexpectedNodesSyntax?.self)) case .specializeAttributeArgumentList: for (index, element) in layout.enumerated() { assertAnyHasNoError(kind, index, [ diff --git a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodes.swift b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodes.swift index adf690a0030..c5d0e4f4fd8 100644 --- a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodes.swift +++ b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodes.swift @@ -16212,6 +16212,7 @@ public struct SameTypeRequirementSyntax: SyntaxProtocol, SyntaxHashable { /// ### Children /// +/// - `shebang`: ``? /// - `statements`: ``CodeBlockItemListSyntax`` /// - `endOfFileToken`: `''` public struct SourceFileSyntax: SyntaxProtocol, SyntaxHashable { @@ -16234,10 +16235,13 @@ public struct SourceFileSyntax: SyntaxProtocol, SyntaxHashable { /// - Parameters: /// - leadingTrivia: Trivia to be prepended to the leading trivia of the node’s first token. If the node is empty, there is no token to attach the trivia to and the parameter is ignored. + /// - shebang: A shebang can specify the path of the compiler when using Swift source file as a script. /// - trailingTrivia: Trivia to be appended to the trailing trivia of the node’s last token. If the node is empty, there is no token to attach the trivia to and the parameter is ignored. public init( leadingTrivia: Trivia? = nil, - _ unexpectedBeforeStatements: UnexpectedNodesSyntax? = nil, + _ unexpectedBeforeShebang: UnexpectedNodesSyntax? = nil, + shebang: TokenSyntax? = nil, + _ unexpectedBetweenShebangAndStatements: UnexpectedNodesSyntax? = nil, statements: CodeBlockItemListSyntax, _ unexpectedBetweenStatementsAndEndOfFileToken: UnexpectedNodesSyntax? = nil, endOfFileToken: TokenSyntax = .endOfFileToken(), @@ -16248,14 +16252,18 @@ public struct SourceFileSyntax: SyntaxProtocol, SyntaxHashable { // Extend the lifetime of all parameters so their arenas don't get destroyed // before they can be added as children of the new arena. let data: SyntaxData = withExtendedLifetime((SyntaxArena(), ( - unexpectedBeforeStatements, + unexpectedBeforeShebang, + shebang, + unexpectedBetweenShebangAndStatements, statements, unexpectedBetweenStatementsAndEndOfFileToken, endOfFileToken, unexpectedAfterEndOfFileToken ))) { (arena, _) in let layout: [RawSyntax?] = [ - unexpectedBeforeStatements?.raw, + unexpectedBeforeShebang?.raw, + shebang?.raw, + unexpectedBetweenShebangAndStatements?.raw, statements.raw, unexpectedBetweenStatementsAndEndOfFileToken?.raw, endOfFileToken.raw, @@ -16274,7 +16282,7 @@ public struct SourceFileSyntax: SyntaxProtocol, SyntaxHashable { self.init(data) } - public var unexpectedBeforeStatements: UnexpectedNodesSyntax? { + public var unexpectedBeforeShebang: UnexpectedNodesSyntax? { get { return data.child(at: 0, parent: Syntax(self)).map(UnexpectedNodesSyntax.init) } @@ -16283,12 +16291,31 @@ public struct SourceFileSyntax: SyntaxProtocol, SyntaxHashable { } } + /// A shebang can specify the path of the compiler when using Swift source file as a script. + public var shebang: TokenSyntax? { + get { + return data.child(at: 1, parent: Syntax(self)).map(TokenSyntax.init) + } + set(value) { + self = SourceFileSyntax(data.replacingChild(at: 1, with: value?.data, arena: SyntaxArena())) + } + } + + public var unexpectedBetweenShebangAndStatements: UnexpectedNodesSyntax? { + get { + return data.child(at: 2, parent: Syntax(self)).map(UnexpectedNodesSyntax.init) + } + set(value) { + self = SourceFileSyntax(data.replacingChild(at: 2, with: value?.data, arena: SyntaxArena())) + } + } + public var statements: CodeBlockItemListSyntax { get { - return CodeBlockItemListSyntax(data.child(at: 1, parent: Syntax(self))!) + return CodeBlockItemListSyntax(data.child(at: 3, parent: Syntax(self))!) } set(value) { - self = SourceFileSyntax(data.replacingChild(at: 1, with: value.data, arena: SyntaxArena())) + self = SourceFileSyntax(data.replacingChild(at: 3, with: value.data, arena: SyntaxArena())) } } @@ -16302,14 +16329,14 @@ public struct SourceFileSyntax: SyntaxProtocol, SyntaxHashable { public func addStatement(_ element: CodeBlockItemSyntax) -> SourceFileSyntax { var collection: RawSyntax let arena = SyntaxArena() - if let col = raw.layoutView!.children[1] { + if let col = raw.layoutView!.children[3] { collection = col.layoutView!.appending(element.raw, arena: arena) } else { collection = RawSyntax.makeLayout(kind: SyntaxKind.codeBlockItemList, from: [element.raw], arena: arena) } let newData = data.replacingChild( - at: 1, + at: 3, with: collection, rawNodeArena: arena, allocationArena: arena @@ -16319,34 +16346,36 @@ public struct SourceFileSyntax: SyntaxProtocol, SyntaxHashable { public var unexpectedBetweenStatementsAndEndOfFileToken: UnexpectedNodesSyntax? { get { - return data.child(at: 2, parent: Syntax(self)).map(UnexpectedNodesSyntax.init) + return data.child(at: 4, parent: Syntax(self)).map(UnexpectedNodesSyntax.init) } set(value) { - self = SourceFileSyntax(data.replacingChild(at: 2, with: value?.data, arena: SyntaxArena())) + self = SourceFileSyntax(data.replacingChild(at: 4, with: value?.data, arena: SyntaxArena())) } } public var endOfFileToken: TokenSyntax { get { - return TokenSyntax(data.child(at: 3, parent: Syntax(self))!) + return TokenSyntax(data.child(at: 5, parent: Syntax(self))!) } set(value) { - self = SourceFileSyntax(data.replacingChild(at: 3, with: value.data, arena: SyntaxArena())) + self = SourceFileSyntax(data.replacingChild(at: 5, with: value.data, arena: SyntaxArena())) } } public var unexpectedAfterEndOfFileToken: UnexpectedNodesSyntax? { get { - return data.child(at: 4, parent: Syntax(self)).map(UnexpectedNodesSyntax.init) + return data.child(at: 6, parent: Syntax(self)).map(UnexpectedNodesSyntax.init) } set(value) { - self = SourceFileSyntax(data.replacingChild(at: 4, with: value?.data, arena: SyntaxArena())) + self = SourceFileSyntax(data.replacingChild(at: 6, with: value?.data, arena: SyntaxArena())) } } public static var structure: SyntaxNodeStructure { return .layout([ - \Self.unexpectedBeforeStatements, + \Self.unexpectedBeforeShebang, + \Self.shebang, + \Self.unexpectedBetweenShebangAndStatements, \Self.statements, \Self.unexpectedBetweenStatementsAndEndOfFileToken, \Self.endOfFileToken, diff --git a/Sources/SwiftSyntaxBuilder/generated/BuildableNodes.swift b/Sources/SwiftSyntaxBuilder/generated/BuildableNodes.swift index ea96496583d..602c52dac94 100644 --- a/Sources/SwiftSyntaxBuilder/generated/BuildableNodes.swift +++ b/Sources/SwiftSyntaxBuilder/generated/BuildableNodes.swift @@ -1177,7 +1177,9 @@ extension SourceFileSyntax { /// A convenience initializer that allows initializing syntax collections using result builders public init( leadingTrivia: Trivia? = nil, - unexpectedBeforeStatements: UnexpectedNodesSyntax? = nil, + unexpectedBeforeShebang: UnexpectedNodesSyntax? = nil, + shebang: TokenSyntax? = nil, + unexpectedBetweenShebangAndStatements: UnexpectedNodesSyntax? = nil, unexpectedBetweenStatementsAndEndOfFileToken: UnexpectedNodesSyntax? = nil, endOfFileToken: TokenSyntax = .endOfFileToken(), unexpectedAfterEndOfFileToken: UnexpectedNodesSyntax? = nil, @@ -1186,7 +1188,9 @@ extension SourceFileSyntax { ) rethrows { try self.init( leadingTrivia: leadingTrivia, - unexpectedBeforeStatements, + unexpectedBeforeShebang, + shebang: shebang, + unexpectedBetweenShebangAndStatements, statements: statementsBuilder(), unexpectedBetweenStatementsAndEndOfFileToken, endOfFileToken: endOfFileToken, diff --git a/Sources/SwiftSyntaxBuilder/generated/RenamedChildrenBuilderCompatibility.swift b/Sources/SwiftSyntaxBuilder/generated/RenamedChildrenBuilderCompatibility.swift index 7a9d25c9d3e..9f1b84e4064 100644 --- a/Sources/SwiftSyntaxBuilder/generated/RenamedChildrenBuilderCompatibility.swift +++ b/Sources/SwiftSyntaxBuilder/generated/RenamedChildrenBuilderCompatibility.swift @@ -750,7 +750,9 @@ extension SourceFileSyntax { /// A convenience initializer that allows initializing syntax collections using result builders public init( leadingTrivia: Trivia? = nil, - unexpectedBeforeStatements: UnexpectedNodesSyntax? = nil, + unexpectedBeforeShebang: UnexpectedNodesSyntax? = nil, + shebang: TokenSyntax? = nil, + unexpectedBetweenShebangAndStatements: UnexpectedNodesSyntax? = nil, unexpectedBetweenStatementsAndEOFToken: UnexpectedNodesSyntax? = nil, eofToken: TokenSyntax = .endOfFileToken(), unexpectedAfterEOFToken: UnexpectedNodesSyntax? = nil, @@ -759,7 +761,9 @@ extension SourceFileSyntax { ) rethrows { try self.init( leadingTrivia: leadingTrivia, - unexpectedBeforeStatements, + unexpectedBeforeShebang, + shebang: shebang, + unexpectedBetweenShebangAndStatements, statements: statementsBuilder(), unexpectedBetweenStatementsAndEOFToken, endOfFileToken: eofToken, diff --git a/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift b/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift index d672494c131..662fed252b1 100644 --- a/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift +++ b/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift @@ -991,7 +991,7 @@ private extension Trivia { switch piece { case .spaces, .tabs: result.append(piece) - case .backslashes, .formfeeds, .pounds, .shebang, .unexpectedText, .verticalTabs: + case .backslashes, .formfeeds, .pounds, .unexpectedText, .verticalTabs: lineContainedNonWhitespaceNonComment = true result.append(piece) case .blockComment, .docBlockComment, .docLineComment, .lineComment: diff --git a/Tests/SwiftParserTest/LexerTests.swift b/Tests/SwiftParserTest/LexerTests.swift index 854765471f7..2a43e45d600 100644 --- a/Tests/SwiftParserTest/LexerTests.swift +++ b/Tests/SwiftParserTest/LexerTests.swift @@ -279,7 +279,8 @@ public class LexerTests: XCTestCase { let x = 42 """, lexemes: [ - LexemeSpec(.keyword, leading: "#!/usr/bin/swiftc\n", text: "let", trailing: " ", flags: [.isAtStartOfLine]), + LexemeSpec(.shebang, text: "#!/usr/bin/swiftc"), + LexemeSpec(.keyword, leading: "\n", text: "let", trailing: " ", flags: [.isAtStartOfLine]), LexemeSpec(.identifier, text: "x", trailing: " "), LexemeSpec(.equal, text: "=", trailing: " "), LexemeSpec(.integerLiteral, text: "42"), diff --git a/Tests/SwiftParserTest/TriviaParserTests.swift b/Tests/SwiftParserTest/TriviaParserTests.swift index 58f2a8b052f..8eb471a7ef0 100644 --- a/Tests/SwiftParserTest/TriviaParserTests.swift +++ b/Tests/SwiftParserTest/TriviaParserTests.swift @@ -39,21 +39,6 @@ final class TriviaParserTests: XCTestCase { ] ) - XCTAssertEqual( - TriviaParser.parseTrivia( - """ - #!/bin/env swift - - - """, - position: .leading - ), - [ - .shebang("#!/bin/env swift"), - .newlines(2), - ] - ) - XCTAssertEqual( TriviaParser.parseTrivia( """ diff --git a/Tests/SwiftParserTest/translated/HashbangLibraryTests.swift b/Tests/SwiftParserTest/translated/HashbangLibraryTests.swift index 8b2e28767ed..4cc668946b7 100644 --- a/Tests/SwiftParserTest/translated/HashbangLibraryTests.swift +++ b/Tests/SwiftParserTest/translated/HashbangLibraryTests.swift @@ -26,6 +26,7 @@ final class HashbangLibraryTests: XCTestCase { class Foo {} """, substructure: SourceFileSyntax( + shebang: .shebang("#!/usr/bin/swift"), statements: CodeBlockItemListSyntax([ CodeBlockItemSyntax( item: .decl( @@ -34,8 +35,7 @@ final class HashbangLibraryTests: XCTestCase { classKeyword: .keyword( .class, leadingTrivia: [ - .shebang("#!/usr/bin/swift"), - .newlines(1), + .newlines(1) ], trailingTrivia: .space ),