Skip to content

Shebang #4120

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 4, 2015
Merged

Shebang #4120

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
43 changes: 43 additions & 0 deletions src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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++;
Expand Down Expand Up @@ -528,6 +538,20 @@ namespace ts {
return pos;
}

const 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
Expand Down Expand Up @@ -617,6 +641,13 @@ 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 {
return shebangTriviaRegex.test(text)
? shebangTriviaRegex.exec(text)[0]
: undefined;
}

export function isIdentifierStart(ch: number, languageVersion: ScriptTarget): boolean {
return ch >= CharacterCodes.A && ch <= CharacterCodes.Z || ch >= CharacterCodes.a && ch <= CharacterCodes.z ||
Expand Down Expand Up @@ -1087,6 +1118,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:
Expand Down
3 changes: 3 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ 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,
SingleLineCommentTrivia,
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,
Expand Down
22 changes: 11 additions & 11 deletions tests/baselines/reference/APISample_linter.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 8 additions & 0 deletions tests/baselines/reference/shebang.js
Original file line number Diff line number Diff line change
@@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, does node ignore shebangs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add tests where the # isn't on the first position. Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 🌹

var foo = 'I wish the generated JS to be executed in node';
5 changes: 5 additions & 0 deletions tests/baselines/reference/shebang.symbols
Original file line number Diff line number Diff line change
@@ -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))

6 changes: 6 additions & 0 deletions tests/baselines/reference/shebang.types
Original file line number Diff line number Diff line change
@@ -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

20 changes: 20 additions & 0 deletions tests/baselines/reference/shebangError.errors.txt
Original file line number Diff line number Diff line change
@@ -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'.
8 changes: 8 additions & 0 deletions tests/baselines/reference/shebangError.js
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 2 additions & 0 deletions tests/cases/compiler/shebang.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/usr/bin/env node
var foo = 'I wish the generated JS to be executed in node';
2 changes: 2 additions & 0 deletions tests/cases/compiler/shebangError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
var foo = 'Shebang is only allowed on the first line';
#!/usr/bin/env node