Skip to content

Commit 0b9470d

Browse files
committed
Implement hashbang syntax
Hashbang is a single-line comment syntax designed to mimic interpreter directive syntax common in command-line scripting. The proposal has reached stage 3 and the syntax is fully supported in SpiderMonkey and V8. See the proposal: https://github.com/tc39/proposal-hashbang This a pretty simple syntax with only a couple of rules. The hashbang acts like a single-line comment except the hashbang sequence (`#!`) must be the first token in the source text.
1 parent de2d7f8 commit 0b9470d

File tree

5 files changed

+132
-0
lines changed

5 files changed

+132
-0
lines changed

lib/Common/ConfigFlagsList.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,7 @@ PHASE(All)
680680
#define DEFAULT_CONFIG_ES2018RegExDotAll (true)
681681
#define DEFAULT_CONFIG_ESBigInt (false)
682682
#define DEFAULT_CONFIG_ESNumericSeparator (true)
683+
#define DEFAULT_CONFIG_ESHashbang (true)
683684
#define DEFAULT_CONFIG_ESSymbolDescription (true)
684685
#define DEFAULT_CONFIG_ESGlobalThis (true)
685686
#ifdef COMPILE_DISABLE_ES6RegExPrototypeProperties
@@ -1219,6 +1220,9 @@ FLAGR(Boolean, ESBigInt, "Enable ESBigInt flag", DEFAULT_CONFIG_ESBigInt)
12191220
// ES Numeric Separator support for numeric constants
12201221
FLAGR(Boolean, ESNumericSeparator, "Enable Numeric Separator flag", DEFAULT_CONFIG_ESNumericSeparator)
12211222

1223+
// ES Hashbang support for interpreter directive syntax
1224+
FLAGR(Boolean, ESHashbang, "Enable Hashbang syntax", DEFAULT_CONFIG_ESHashbang)
1225+
12221226
// ES Symbol.prototype.description flag
12231227
FLAGR(Boolean, ESSymbolDescription, "Enable Symbol.prototype.description", DEFAULT_CONFIG_ESSymbolDescription)
12241228

lib/Parser/Scan.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1646,6 +1646,7 @@ tokens Scanner<EncodingPolicy>::ScanCore(bool identifyKwds)
16461646
#endif
16471647
switch (ch)
16481648
{
1649+
LDefault:
16491650
default:
16501651
if (ch == kchLS ||
16511652
ch == kchPS )
@@ -1961,6 +1962,16 @@ tokens Scanner<EncodingPolicy>::ScanCore(bool identifyKwds)
19611962
}
19621963
}
19631964
break;
1965+
1966+
case '#':
1967+
// Hashbang syntax is a single line comment only if it is the first token in the source
1968+
if (m_scriptContext->GetConfig()->IsESHashbangEnabled() && this->PeekFirst(p, last) == '!' && m_pchBase == m_pchMinTok)
1969+
{
1970+
p++;
1971+
goto LSkipLineComment;
1972+
}
1973+
goto LDefault;
1974+
19641975
case '/':
19651976
token = tkDiv;
19661977
switch(this->PeekFirst(p, last))

lib/Runtime/Base/ThreadConfigFlagsList.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ FLAG_RELEASE(IsESSharedArrayBufferEnabled, ESSharedArrayBuffer)
4949
FLAG_RELEASE(IsESDynamicImportEnabled, ESDynamicImport)
5050
FLAG_RELEASE(IsESBigIntEnabled, ESBigInt)
5151
FLAG_RELEASE(IsESNumericSeparatorEnabled, ESNumericSeparator)
52+
FLAG_RELEASE(IsESHashbangEnabled, ESHashbang)
5253
FLAG_RELEASE(IsESExportNsAsEnabled, ESExportNsAs)
5354
FLAG_RELEASE(IsESSymbolDescriptionEnabled, ESSymbolDescription)
5455
FLAG_RELEASE(IsESGlobalThisEnabled, ESGlobalThis)

test/Scanner/Hashbang.js

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//-------------------------------------------------------------------------------------------------------
2+
// Copyright (C) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
4+
//-------------------------------------------------------------------------------------------------------
5+
6+
WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
7+
8+
let invalidScripts = [
9+
[" #!\n", "Hashbang must be the first token (even before whitespace)"],
10+
["\n#!\n", "Hashbang must be the first token (even before whitespace)"],
11+
["##!\n", "Hashbang token is '#!'"],
12+
[";#!\n", "Hashbang must be the first token"],
13+
["'use strict'#!\n", "Hashbang must come before 'use strict'"],
14+
["#!\n#!\n", "Only one hashbang may exist because it has to be the first token"],
15+
["function foo() {\n#!\n}", "Hashbang must be the first token in the script (not local function)"],
16+
["function foo() {#!\n}", "Hashbang must be the first token in the script (not local function)"],
17+
["#\\041\n", "Hashbang can't be made of encoded characters"],
18+
["#\\u0021\n", "Hashbang can't be made of encoded characters"],
19+
["#\\u{21}\n", "Hashbang can't be made of encoded characters"],
20+
["#\\x21\n", "Hashbang can't be made of encoded characters"],
21+
["\\043!\n", "Hashbang can't be made of encoded characters"],
22+
["\\u0023!\n", "Hashbang can't be made of encoded characters"],
23+
["\\u{23}!\n", "Hashbang can't be made of encoded characters"],
24+
["\\x23!\n", "Hashbang can't be made of encoded characters"],
25+
["\\u0023\\u0021\n", "Hashbang can't be made of encoded characters"],
26+
["Function('#!\n','')", "Hashbang is not valid in function evaluator contexts"],
27+
["new Function('#!\n','')", "Hashbang is not valid in function evaluator contexts"],
28+
["{\n#!\n}\n", "Hashbang not valid in block"],
29+
["#!/*\nthrow 123;\n*/\nthrow 456;", "Hashbang comments out a single line"],
30+
["\\\\ single line comment\n#! hashbang\n", "Single line comment may not preceed hashbang"],
31+
["/**/#! hashbang\n", "Multi-line comment may not preceed hashbang"],
32+
["/**/\n#! hashbang\n", "Multi-line comment may not preceed hashbang"],
33+
];
34+
35+
var tests = [
36+
{
37+
name: "Valid hashbang in ordinary script",
38+
body: function () {
39+
assert.areEqual(2, WScript.LoadScript("#! throw 'error';\nthis.prop=2;").prop);
40+
assert.areEqual(3, WScript.LoadScript("#! throw 'error'\u{000D}this.prop=3;").prop);
41+
assert.areEqual(4, WScript.LoadScript("#! throw 'error'\u{2028}this.prop=4;").prop);
42+
assert.areEqual(5, WScript.LoadScript("#! throw 'error'\u{2029}this.prop=5;").prop);
43+
}
44+
},
45+
{
46+
name: "Valid hashbang in module script",
47+
body: function () {
48+
WScript.RegisterModuleSource('module_hashbang_valid.js', "#! export default 123;\n export default 456;");
49+
50+
testRunner.LoadModule(`
51+
import {default as prop} from 'module_hashbang_valid.js';
52+
assert.areEqual(456, prop);
53+
`, 'samethread', false, false);
54+
}
55+
},
56+
{
57+
name: "Valid hashbang in eval",
58+
body: function () {
59+
assert.areEqual(undefined, eval('#!'));
60+
assert.areEqual(undefined, eval('#!\n'));
61+
assert.areEqual(1, eval('#!\n1'));
62+
assert.areEqual(undefined, eval('#!2\n'));
63+
}
64+
},
65+
{
66+
name: "Valid hashbang in indirect eval",
67+
body: function () {
68+
let _eval = eval;
69+
assert.areEqual(undefined, _eval('#!'));
70+
assert.areEqual(undefined, _eval('#!\n'));
71+
assert.areEqual(1, _eval('#!\n1'));
72+
assert.areEqual(undefined, _eval('#!2\n'));
73+
}
74+
},
75+
{
76+
name: "Invalid hashbang in ordinary script",
77+
body: function () {
78+
for (a of invalidScripts) {
79+
assert.throws(()=>WScript.LoadScript(a[0]), SyntaxError, a[1]);
80+
}
81+
}
82+
},
83+
{
84+
name: "Invalid hashbang in module script",
85+
body: function () {
86+
for (a of invalidScripts) {
87+
assert.throws(()=>WScript.LoadModule(a[0]), SyntaxError, a[1]);
88+
}
89+
}
90+
},
91+
{
92+
name: "Invalid hashbang in eval",
93+
body: function () {
94+
for (a of invalidScripts) {
95+
assert.throws(()=>eval(a[0]), SyntaxError, a[1]);
96+
}
97+
}
98+
},
99+
{
100+
name: "Invalid hashbang in indirect eval",
101+
body: function () {
102+
let _eval = eval;
103+
for (a of invalidScripts) {
104+
assert.throws(()=>_eval(a[0]), SyntaxError, a[1]);
105+
}
106+
}
107+
},
108+
];
109+
110+
testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });

test/Scanner/rlexe.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,10 @@
1212
<baseline>InvalidCharacter.baseline</baseline>
1313
</default>
1414
</test>
15+
<test>
16+
<default>
17+
<files>Hashbang.js</files>
18+
<compile-flags>-args summary -endargs -ESHashbang</compile-flags>
19+
</default>
20+
</test>
1521
</regress-exe>

0 commit comments

Comments
 (0)