diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index d52f96c912b..b0ce58ace8b 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -401,6 +401,9 @@ namespace ts { case CharacterCodes.greaterThan: // Starts of conflict marker trivia return true; + case CharacterCodes.hash: + // Only if its the beginning can we have #! trivia + return pos === 0; default: return ch > CharacterCodes.maxAsciiCharacter; } @@ -461,6 +464,13 @@ namespace ts { } break; + case CharacterCodes.hash: + if (isShebangTrivia(text, pos)) { + pos = scanShebangTrivia(text, pos); + continue; + } + break; + default: if (ch > CharacterCodes.maxAsciiCharacter && (isWhiteSpace(ch) || isLineBreak(ch))) { pos++; @@ -528,6 +538,20 @@ namespace ts { return pos; } + let shebangTriviaRegex = /^#!.*/; + + function isShebangTrivia(text: string, pos: number) { + // Shebangs check must only be done at the start of the file + Debug.assert(pos === 0); + return shebangTriviaRegex.test(text); + } + + function scanShebangTrivia(text: string, pos: number) { + let shebang = shebangTriviaRegex.exec(text)[0]; + pos = pos + shebang.length; + return pos; + } + // Extract comments from the given source text starting at the given position. If trailing is // false, whitespace is skipped until the first line break and comments between that location // and the next token are returned.If trailing is true, comments occurring between the given @@ -1087,6 +1111,18 @@ namespace ts { return token = SyntaxKind.EndOfFileToken; } let ch = text.charCodeAt(pos); + + // Special handling for shebang + if (ch == CharacterCodes.hash && pos === 0 && isShebangTrivia(text, pos)) { + pos = scanShebangTrivia(text ,pos); + if (skipTrivia) { + continue; + } + else { + return token = SyntaxKind.ShebangTrivia; + } + } + switch (ch) { case CharacterCodes.lineFeed: case CharacterCodes.carriageReturn: diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c5d7f879c25..da43984ee13 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -17,6 +17,7 @@ namespace ts { } // token > SyntaxKind.Identifer => token is a keyword + // Also, If you add a new SyntaxKind be sure to keep the `Markers` section at the bottom in sync export const enum SyntaxKind { Unknown, EndOfFileToken, @@ -24,6 +25,8 @@ namespace ts { MultiLineCommentTrivia, NewLineTrivia, WhitespaceTrivia, + // We detect and preserve #! on the first line + ShebangTrivia, // We detect and provide better error recovery when we encounter a git merge marker. This // allows us to edit files with git-conflict markers in them in a much more pleasant manner. ConflictMarkerTrivia, diff --git a/tests/baselines/reference/APISample_linter.js b/tests/baselines/reference/APISample_linter.js index 30951b90fca..9d44dfdc03e 100644 --- a/tests/baselines/reference/APISample_linter.js +++ b/tests/baselines/reference/APISample_linter.js @@ -75,28 +75,28 @@ function delint(sourceFile) { delintNode(sourceFile); function delintNode(node) { switch (node.kind) { - case 196 /* ForStatement */: - case 197 /* ForInStatement */: - case 195 /* WhileStatement */: - case 194 /* DoStatement */: - if (node.statement.kind !== 189 /* Block */) { + case 197 /* ForStatement */: + case 198 /* ForInStatement */: + case 196 /* WhileStatement */: + case 195 /* DoStatement */: + if (node.statement.kind !== 190 /* Block */) { report(node, "A looping statement's contents should be wrapped in a block body."); } break; - case 193 /* IfStatement */: + case 194 /* IfStatement */: var ifStatement = node; - if (ifStatement.thenStatement.kind !== 189 /* Block */) { + if (ifStatement.thenStatement.kind !== 190 /* Block */) { report(ifStatement.thenStatement, "An if statement's contents should be wrapped in a block body."); } if (ifStatement.elseStatement && - ifStatement.elseStatement.kind !== 189 /* Block */ && - ifStatement.elseStatement.kind !== 193 /* IfStatement */) { + ifStatement.elseStatement.kind !== 190 /* Block */ && + ifStatement.elseStatement.kind !== 194 /* IfStatement */) { report(ifStatement.elseStatement, "An else statement's contents should be wrapped in a block body."); } break; - case 178 /* BinaryExpression */: + case 179 /* BinaryExpression */: var op = node.operatorToken.kind; - if (op === 29 /* EqualsEqualsToken */ || op == 30 /* ExclamationEqualsToken */) { + if (op === 30 /* EqualsEqualsToken */ || op == 31 /* ExclamationEqualsToken */) { report(node, "Use '===' and '!=='."); } break;