From 6ea64637e150e6d2dadd8e12f1e2ec6ed0b95002 Mon Sep 17 00:00:00 2001 From: basarat Date: Sat, 1 Aug 2015 18:52:15 +1000 Subject: [PATCH 1/5] scanner for trivia + accept baselines --- src/compiler/scanner.ts | 36 +++++++++++++++++++ src/compiler/types.ts | 3 ++ tests/baselines/reference/APISample_linter.js | 22 ++++++------ 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index d52f96c912b97..b0ce58ace8b72 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 c5d7f879c25fb..da43984ee1377 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 30951b90fca68..9d44dfdc03e98 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; From 82d5a9c1bb1c35050e733c92cb57e28b9d71836d Mon Sep 17 00:00:00 2001 From: basarat Date: Sun, 2 Aug 2015 11:25:55 +1000 Subject: [PATCH 2/5] test(shebang) desired outcome --- tests/baselines/reference/shebang.js | 8 ++++++++ tests/baselines/reference/shebang.symbols | 5 +++++ tests/baselines/reference/shebang.types | 6 ++++++ tests/cases/compiler/shebang.ts | 2 ++ 4 files changed, 21 insertions(+) create mode 100644 tests/baselines/reference/shebang.js create mode 100644 tests/baselines/reference/shebang.symbols create mode 100644 tests/baselines/reference/shebang.types create mode 100644 tests/cases/compiler/shebang.ts diff --git a/tests/baselines/reference/shebang.js b/tests/baselines/reference/shebang.js new file mode 100644 index 0000000000000..d7e3a4d835158 --- /dev/null +++ b/tests/baselines/reference/shebang.js @@ -0,0 +1,8 @@ +//// [shebang.ts] +#!/usr/bin/env node +var foo = 'I wish the generated JS to be executed in node'; + + +//// [shebang.js] +#!/usr/bin/env node +var foo = 'I wish the generated JS to be executed in node'; diff --git a/tests/baselines/reference/shebang.symbols b/tests/baselines/reference/shebang.symbols new file mode 100644 index 0000000000000..37ad543667475 --- /dev/null +++ b/tests/baselines/reference/shebang.symbols @@ -0,0 +1,5 @@ +=== tests/cases/compiler/shebang.ts === +#!/usr/bin/env node +var foo = 'I wish the generated JS to be executed in node'; +>foo : Symbol(foo, Decl(shebang.ts, 1, 3)) + diff --git a/tests/baselines/reference/shebang.types b/tests/baselines/reference/shebang.types new file mode 100644 index 0000000000000..5fd6f0533ec52 --- /dev/null +++ b/tests/baselines/reference/shebang.types @@ -0,0 +1,6 @@ +=== tests/cases/compiler/shebang.ts === +#!/usr/bin/env node +var foo = 'I wish the generated JS to be executed in node'; +>foo : string +>'I wish the generated JS to be executed in node' : string + diff --git a/tests/cases/compiler/shebang.ts b/tests/cases/compiler/shebang.ts new file mode 100644 index 0000000000000..3456df0fad5a1 --- /dev/null +++ b/tests/cases/compiler/shebang.ts @@ -0,0 +1,2 @@ +#!/usr/bin/env node +var foo = 'I wish the generated JS to be executed in node'; From 5fcf3d7c902d6e96e81f7dc0880c7e97e451439d Mon Sep 17 00:00:00 2001 From: basarat Date: Sun, 2 Aug 2015 12:24:18 +1000 Subject: [PATCH 3/5] emit shebang --- src/compiler/emitter.ts | 8 ++++++++ src/compiler/scanner.ts | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index bef04e5030264..cb5eb864660a7 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -6565,6 +6565,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi function emitSourceFileNode(node: SourceFile) { // Start new file on new line writeLine(); + emitShebang(); emitDetachedComments(node); // emit prologue directives prior to __extends @@ -6986,6 +6987,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } } } + + function emitShebang() { + let shebang = getShebang(currentSourceFile.text); + if (shebang) { + write(shebang); + } + } function isPinnedOrTripleSlashComment(comment: CommentRange) { if (currentSourceFile.text.charCodeAt(comment.pos + 1) === CharacterCodes.asterisk) { diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index b0ce58ace8b72..01efc96a86f93 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -641,6 +641,16 @@ namespace ts { export function getTrailingCommentRanges(text: string, pos: number): CommentRange[] { return getCommentRanges(text, pos, /*trailing*/ true); } + + /** Optionally, get the shebang */ + export function getShebang(text: string): string { + if (!shebangTriviaRegex.test(text)) { + return undefined; + } + else { + return shebangTriviaRegex.exec(text)[0]; + } + } export function isIdentifierStart(ch: number, languageVersion: ScriptTarget): boolean { return ch >= CharacterCodes.A && ch <= CharacterCodes.Z || ch >= CharacterCodes.a && ch <= CharacterCodes.z || From 9754ec10e2f8f9906bc8f21f5b2744fd919ea0c2 Mon Sep 17 00:00:00 2001 From: Basarat Syed Date: Mon, 3 Aug 2015 11:20:43 +1000 Subject: [PATCH 4/5] test(shebang) error case --- .../reference/shebangError.errors.txt | 20 +++++++++++++++++++ tests/baselines/reference/shebangError.js | 8 ++++++++ tests/cases/compiler/shebangError.ts | 2 ++ 3 files changed, 30 insertions(+) create mode 100644 tests/baselines/reference/shebangError.errors.txt create mode 100644 tests/baselines/reference/shebangError.js create mode 100644 tests/cases/compiler/shebangError.ts diff --git a/tests/baselines/reference/shebangError.errors.txt b/tests/baselines/reference/shebangError.errors.txt new file mode 100644 index 0000000000000..e8197d8bc5f53 --- /dev/null +++ b/tests/baselines/reference/shebangError.errors.txt @@ -0,0 +1,20 @@ +tests/cases/compiler/shebangError.ts(2,1): error TS1127: Invalid character. +tests/cases/compiler/shebangError.ts(2,2): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. +tests/cases/compiler/shebangError.ts(2,12): error TS2304: Cannot find name 'env'. +tests/cases/compiler/shebangError.ts(2,16): error TS1005: ';' expected. +tests/cases/compiler/shebangError.ts(2,16): error TS2304: Cannot find name 'node'. + + +==== tests/cases/compiler/shebangError.ts (5 errors) ==== + var foo = 'Shebang is only allowed on the first line'; + #!/usr/bin/env node + +!!! error TS1127: Invalid character. + ~~~~~~~~~ +!!! error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. + ~~~ +!!! error TS2304: Cannot find name 'env'. + ~~~~ +!!! error TS1005: ';' expected. + ~~~~ +!!! error TS2304: Cannot find name 'node'. \ No newline at end of file diff --git a/tests/baselines/reference/shebangError.js b/tests/baselines/reference/shebangError.js new file mode 100644 index 0000000000000..a8fb376d8a90c --- /dev/null +++ b/tests/baselines/reference/shebangError.js @@ -0,0 +1,8 @@ +//// [shebangError.ts] +var foo = 'Shebang is only allowed on the first line'; +#!/usr/bin/env node + +//// [shebangError.js] +var foo = 'Shebang is only allowed on the first line'; +!/usr/bin / env; +node; diff --git a/tests/cases/compiler/shebangError.ts b/tests/cases/compiler/shebangError.ts new file mode 100644 index 0000000000000..91a27a65aa009 --- /dev/null +++ b/tests/cases/compiler/shebangError.ts @@ -0,0 +1,2 @@ +var foo = 'Shebang is only allowed on the first line'; +#!/usr/bin/env node \ No newline at end of file From e0a7627f12844d0a2c4827bf903881c595c7134b Mon Sep 17 00:00:00 2001 From: Basarat Syed Date: Mon, 3 Aug 2015 11:28:47 +1000 Subject: [PATCH 5/5] CR feedback --- src/compiler/scanner.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 01efc96a86f93..379177a9b9cc6 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -538,7 +538,7 @@ namespace ts { return pos; } - let shebangTriviaRegex = /^#!.*/; + const shebangTriviaRegex = /^#!.*/; function isShebangTrivia(text: string, pos: number) { // Shebangs check must only be done at the start of the file @@ -644,12 +644,9 @@ namespace ts { /** Optionally, get the shebang */ export function getShebang(text: string): string { - if (!shebangTriviaRegex.test(text)) { - return undefined; - } - else { - return shebangTriviaRegex.exec(text)[0]; - } + return shebangTriviaRegex.test(text) + ? shebangTriviaRegex.exec(text)[0] + : undefined; } export function isIdentifierStart(ch: number, languageVersion: ScriptTarget): boolean { @@ -1123,7 +1120,7 @@ namespace ts { let ch = text.charCodeAt(pos); // Special handling for shebang - if (ch == CharacterCodes.hash && pos === 0 && isShebangTrivia(text, pos)) { + if (ch === CharacterCodes.hash && pos === 0 && isShebangTrivia(text, pos)) { pos = scanShebangTrivia(text ,pos); if (skipTrivia) { continue;