Skip to content

Commit d855f47

Browse files
committed
Merge pull request #4120 from basarat/feat/shebang
Shebang
2 parents 4c7b214 + e0a7627 commit d855f47

File tree

11 files changed

+116
-11
lines changed

11 files changed

+116
-11
lines changed

src/compiler/emitter.ts

+8
Original file line numberDiff line numberDiff line change
@@ -6565,6 +6565,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
65656565
function emitSourceFileNode(node: SourceFile) {
65666566
// Start new file on new line
65676567
writeLine();
6568+
emitShebang();
65686569
emitDetachedComments(node);
65696570

65706571
// emit prologue directives prior to __extends
@@ -6986,6 +6987,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
69866987
}
69876988
}
69886989
}
6990+
6991+
function emitShebang() {
6992+
let shebang = getShebang(currentSourceFile.text);
6993+
if (shebang) {
6994+
write(shebang);
6995+
}
6996+
}
69896997

69906998
function isPinnedOrTripleSlashComment(comment: CommentRange) {
69916999
if (currentSourceFile.text.charCodeAt(comment.pos + 1) === CharacterCodes.asterisk) {

src/compiler/scanner.ts

+43
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,9 @@ namespace ts {
401401
case CharacterCodes.greaterThan:
402402
// Starts of conflict marker trivia
403403
return true;
404+
case CharacterCodes.hash:
405+
// Only if its the beginning can we have #! trivia
406+
return pos === 0;
404407
default:
405408
return ch > CharacterCodes.maxAsciiCharacter;
406409
}
@@ -461,6 +464,13 @@ namespace ts {
461464
}
462465
break;
463466

467+
case CharacterCodes.hash:
468+
if (isShebangTrivia(text, pos)) {
469+
pos = scanShebangTrivia(text, pos);
470+
continue;
471+
}
472+
break;
473+
464474
default:
465475
if (ch > CharacterCodes.maxAsciiCharacter && (isWhiteSpace(ch) || isLineBreak(ch))) {
466476
pos++;
@@ -528,6 +538,20 @@ namespace ts {
528538
return pos;
529539
}
530540

541+
const shebangTriviaRegex = /^#!.*/;
542+
543+
function isShebangTrivia(text: string, pos: number) {
544+
// Shebangs check must only be done at the start of the file
545+
Debug.assert(pos === 0);
546+
return shebangTriviaRegex.test(text);
547+
}
548+
549+
function scanShebangTrivia(text: string, pos: number) {
550+
let shebang = shebangTriviaRegex.exec(text)[0];
551+
pos = pos + shebang.length;
552+
return pos;
553+
}
554+
531555
// Extract comments from the given source text starting at the given position. If trailing is
532556
// false, whitespace is skipped until the first line break and comments between that location
533557
// and the next token are returned.If trailing is true, comments occurring between the given
@@ -617,6 +641,13 @@ namespace ts {
617641
export function getTrailingCommentRanges(text: string, pos: number): CommentRange[] {
618642
return getCommentRanges(text, pos, /*trailing*/ true);
619643
}
644+
645+
/** Optionally, get the shebang */
646+
export function getShebang(text: string): string {
647+
return shebangTriviaRegex.test(text)
648+
? shebangTriviaRegex.exec(text)[0]
649+
: undefined;
650+
}
620651

621652
export function isIdentifierStart(ch: number, languageVersion: ScriptTarget): boolean {
622653
return ch >= CharacterCodes.A && ch <= CharacterCodes.Z || ch >= CharacterCodes.a && ch <= CharacterCodes.z ||
@@ -1087,6 +1118,18 @@ namespace ts {
10871118
return token = SyntaxKind.EndOfFileToken;
10881119
}
10891120
let ch = text.charCodeAt(pos);
1121+
1122+
// Special handling for shebang
1123+
if (ch === CharacterCodes.hash && pos === 0 && isShebangTrivia(text, pos)) {
1124+
pos = scanShebangTrivia(text ,pos);
1125+
if (skipTrivia) {
1126+
continue;
1127+
}
1128+
else {
1129+
return token = SyntaxKind.ShebangTrivia;
1130+
}
1131+
}
1132+
10901133
switch (ch) {
10911134
case CharacterCodes.lineFeed:
10921135
case CharacterCodes.carriageReturn:

src/compiler/types.ts

+3
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,16 @@ namespace ts {
1717
}
1818

1919
// token > SyntaxKind.Identifer => token is a keyword
20+
// Also, If you add a new SyntaxKind be sure to keep the `Markers` section at the bottom in sync
2021
export const enum SyntaxKind {
2122
Unknown,
2223
EndOfFileToken,
2324
SingleLineCommentTrivia,
2425
MultiLineCommentTrivia,
2526
NewLineTrivia,
2627
WhitespaceTrivia,
28+
// We detect and preserve #! on the first line
29+
ShebangTrivia,
2730
// We detect and provide better error recovery when we encounter a git merge marker. This
2831
// allows us to edit files with git-conflict markers in them in a much more pleasant manner.
2932
ConflictMarkerTrivia,

tests/baselines/reference/APISample_linter.js

+11-11
Original file line numberDiff line numberDiff line change
@@ -75,28 +75,28 @@ function delint(sourceFile) {
7575
delintNode(sourceFile);
7676
function delintNode(node) {
7777
switch (node.kind) {
78-
case 196 /* ForStatement */:
79-
case 197 /* ForInStatement */:
80-
case 195 /* WhileStatement */:
81-
case 194 /* DoStatement */:
82-
if (node.statement.kind !== 189 /* Block */) {
78+
case 197 /* ForStatement */:
79+
case 198 /* ForInStatement */:
80+
case 196 /* WhileStatement */:
81+
case 195 /* DoStatement */:
82+
if (node.statement.kind !== 190 /* Block */) {
8383
report(node, "A looping statement's contents should be wrapped in a block body.");
8484
}
8585
break;
86-
case 193 /* IfStatement */:
86+
case 194 /* IfStatement */:
8787
var ifStatement = node;
88-
if (ifStatement.thenStatement.kind !== 189 /* Block */) {
88+
if (ifStatement.thenStatement.kind !== 190 /* Block */) {
8989
report(ifStatement.thenStatement, "An if statement's contents should be wrapped in a block body.");
9090
}
9191
if (ifStatement.elseStatement &&
92-
ifStatement.elseStatement.kind !== 189 /* Block */ &&
93-
ifStatement.elseStatement.kind !== 193 /* IfStatement */) {
92+
ifStatement.elseStatement.kind !== 190 /* Block */ &&
93+
ifStatement.elseStatement.kind !== 194 /* IfStatement */) {
9494
report(ifStatement.elseStatement, "An else statement's contents should be wrapped in a block body.");
9595
}
9696
break;
97-
case 178 /* BinaryExpression */:
97+
case 179 /* BinaryExpression */:
9898
var op = node.operatorToken.kind;
99-
if (op === 29 /* EqualsEqualsToken */ || op == 30 /* ExclamationEqualsToken */) {
99+
if (op === 30 /* EqualsEqualsToken */ || op == 31 /* ExclamationEqualsToken */) {
100100
report(node, "Use '===' and '!=='.");
101101
}
102102
break;

tests/baselines/reference/shebang.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//// [shebang.ts]
2+
#!/usr/bin/env node
3+
var foo = 'I wish the generated JS to be executed in node';
4+
5+
6+
//// [shebang.js]
7+
#!/usr/bin/env node
8+
var foo = 'I wish the generated JS to be executed in node';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
=== tests/cases/compiler/shebang.ts ===
2+
#!/usr/bin/env node
3+
var foo = 'I wish the generated JS to be executed in node';
4+
>foo : Symbol(foo, Decl(shebang.ts, 1, 3))
5+
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
=== tests/cases/compiler/shebang.ts ===
2+
#!/usr/bin/env node
3+
var foo = 'I wish the generated JS to be executed in node';
4+
>foo : string
5+
>'I wish the generated JS to be executed in node' : string
6+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
tests/cases/compiler/shebangError.ts(2,1): error TS1127: Invalid character.
2+
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.
3+
tests/cases/compiler/shebangError.ts(2,12): error TS2304: Cannot find name 'env'.
4+
tests/cases/compiler/shebangError.ts(2,16): error TS1005: ';' expected.
5+
tests/cases/compiler/shebangError.ts(2,16): error TS2304: Cannot find name 'node'.
6+
7+
8+
==== tests/cases/compiler/shebangError.ts (5 errors) ====
9+
var foo = 'Shebang is only allowed on the first line';
10+
#!/usr/bin/env node
11+
12+
!!! error TS1127: Invalid character.
13+
~~~~~~~~~
14+
!!! error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type.
15+
~~~
16+
!!! error TS2304: Cannot find name 'env'.
17+
~~~~
18+
!!! error TS1005: ';' expected.
19+
~~~~
20+
!!! error TS2304: Cannot find name 'node'.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//// [shebangError.ts]
2+
var foo = 'Shebang is only allowed on the first line';
3+
#!/usr/bin/env node
4+
5+
//// [shebangError.js]
6+
var foo = 'Shebang is only allowed on the first line';
7+
!/usr/bin / env;
8+
node;

tests/cases/compiler/shebang.ts

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/usr/bin/env node
2+
var foo = 'I wish the generated JS to be executed in node';

tests/cases/compiler/shebangError.ts

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
var foo = 'Shebang is only allowed on the first line';
2+
#!/usr/bin/env node

0 commit comments

Comments
 (0)