Skip to content
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

Implement hashbang syntax #6145

Merged
merged 1 commit into from
Jun 5, 2019
Merged
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
4 changes: 4 additions & 0 deletions lib/Common/ConfigFlagsList.h
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,7 @@ PHASE(All)
#define DEFAULT_CONFIG_ES2018RegExDotAll (true)
#define DEFAULT_CONFIG_ESBigInt (false)
#define DEFAULT_CONFIG_ESNumericSeparator (true)
#define DEFAULT_CONFIG_ESHashbang (true)
#define DEFAULT_CONFIG_ESSymbolDescription (true)
#define DEFAULT_CONFIG_ESGlobalThis (true)
#ifdef COMPILE_DISABLE_ES6RegExPrototypeProperties
Expand Down Expand Up @@ -1219,6 +1220,9 @@ FLAGR(Boolean, ESBigInt, "Enable ESBigInt flag", DEFAULT_CONFIG_ESBigInt)
// ES Numeric Separator support for numeric constants
FLAGR(Boolean, ESNumericSeparator, "Enable Numeric Separator flag", DEFAULT_CONFIG_ESNumericSeparator)

// ES Hashbang support for interpreter directive syntax
FLAGR(Boolean, ESHashbang, "Enable Hashbang syntax", DEFAULT_CONFIG_ESHashbang)

// ES Symbol.prototype.description flag
FLAGR(Boolean, ESSymbolDescription, "Enable Symbol.prototype.description", DEFAULT_CONFIG_ESSymbolDescription)

Expand Down
11 changes: 11 additions & 0 deletions lib/Parser/Scan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1646,6 +1646,7 @@ tokens Scanner<EncodingPolicy>::ScanCore(bool identifyKwds)
#endif
switch (ch)
{
LDefault:
default:
if (ch == kchLS ||
ch == kchPS )
Expand Down Expand Up @@ -1961,6 +1962,16 @@ tokens Scanner<EncodingPolicy>::ScanCore(bool identifyKwds)
}
}
break;

case '#':
// Hashbang syntax is a single line comment only if it is the first token in the source
if (m_scriptContext->GetConfig()->IsESHashbangEnabled() && this->PeekFirst(p, last) == '!' && m_pchBase == m_pchMinTok)
{
p++;
goto LSkipLineComment;
}
goto LDefault;

case '/':
token = tkDiv;
switch(this->PeekFirst(p, last))
Expand Down
1 change: 1 addition & 0 deletions lib/Runtime/Base/ThreadConfigFlagsList.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ FLAG_RELEASE(IsESSharedArrayBufferEnabled, ESSharedArrayBuffer)
FLAG_RELEASE(IsESDynamicImportEnabled, ESDynamicImport)
FLAG_RELEASE(IsESBigIntEnabled, ESBigInt)
FLAG_RELEASE(IsESNumericSeparatorEnabled, ESNumericSeparator)
FLAG_RELEASE(IsESHashbangEnabled, ESHashbang)
FLAG_RELEASE(IsESExportNsAsEnabled, ESExportNsAs)
FLAG_RELEASE(IsESSymbolDescriptionEnabled, ESSymbolDescription)
FLAG_RELEASE(IsESGlobalThisEnabled, ESGlobalThis)
Expand Down
110 changes: 110 additions & 0 deletions test/Scanner/Hashbang.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------

WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");

let invalidScripts = [
[" #!\n", "Hashbang must be the first token (even before whitespace)"],
["\n#!\n", "Hashbang must be the first token (even before whitespace)"],
["##!\n", "Hashbang token is '#!'"],
[";#!\n", "Hashbang must be the first token"],
["'use strict'#!\n", "Hashbang must come before 'use strict'"],
["#!\n#!\n", "Only one hashbang may exist because it has to be the first token"],
["function foo() {\n#!\n}", "Hashbang must be the first token in the script (not local function)"],
["function foo() {#!\n}", "Hashbang must be the first token in the script (not local function)"],
["#\\041\n", "Hashbang can't be made of encoded characters"],
["#\\u0021\n", "Hashbang can't be made of encoded characters"],
["#\\u{21}\n", "Hashbang can't be made of encoded characters"],
["#\\x21\n", "Hashbang can't be made of encoded characters"],
["\\043!\n", "Hashbang can't be made of encoded characters"],
["\\u0023!\n", "Hashbang can't be made of encoded characters"],
["\\u{23}!\n", "Hashbang can't be made of encoded characters"],
["\\x23!\n", "Hashbang can't be made of encoded characters"],
["\\u0023\\u0021\n", "Hashbang can't be made of encoded characters"],
["Function('#!\n','')", "Hashbang is not valid in function evaluator contexts"],
["new Function('#!\n','')", "Hashbang is not valid in function evaluator contexts"],
["{\n#!\n}\n", "Hashbang not valid in block"],
["#!/*\nthrow 123;\n*/\nthrow 456;", "Hashbang comments out a single line"],
["\\\\ single line comment\n#! hashbang\n", "Single line comment may not preceed hashbang"],
["/**/#! hashbang\n", "Multi-line comment may not preceed hashbang"],
["/**/\n#! hashbang\n", "Multi-line comment may not preceed hashbang"],
];

var tests = [
{
name: "Valid hashbang in ordinary script",
body: function () {
assert.areEqual(2, WScript.LoadScript("#! throw 'error';\nthis.prop=2;").prop);
assert.areEqual(3, WScript.LoadScript("#! throw 'error'\u{000D}this.prop=3;").prop);
assert.areEqual(4, WScript.LoadScript("#! throw 'error'\u{2028}this.prop=4;").prop);
assert.areEqual(5, WScript.LoadScript("#! throw 'error'\u{2029}this.prop=5;").prop);
}
},
{
name: "Valid hashbang in module script",
body: function () {
WScript.RegisterModuleSource('module_hashbang_valid.js', "#! export default 123;\n export default 456;");

testRunner.LoadModule(`
import {default as prop} from 'module_hashbang_valid.js';
assert.areEqual(456, prop);
`, 'samethread', false, false);
}
},
{
name: "Valid hashbang in eval",
body: function () {
assert.areEqual(undefined, eval('#!'));
assert.areEqual(undefined, eval('#!\n'));
assert.areEqual(1, eval('#!\n1'));
assert.areEqual(undefined, eval('#!2\n'));
}
},
{
name: "Valid hashbang in indirect eval",
body: function () {
let _eval = eval;
assert.areEqual(undefined, _eval('#!'));
assert.areEqual(undefined, _eval('#!\n'));
assert.areEqual(1, _eval('#!\n1'));
assert.areEqual(undefined, _eval('#!2\n'));
}
},
{
name: "Invalid hashbang in ordinary script",
body: function () {
for (a of invalidScripts) {
assert.throws(()=>WScript.LoadScript(a[0]), SyntaxError, a[1]);
}
}
},
{
name: "Invalid hashbang in module script",
body: function () {
for (a of invalidScripts) {
assert.throws(()=>WScript.LoadModule(a[0]), SyntaxError, a[1]);
}
}
},
{
name: "Invalid hashbang in eval",
body: function () {
for (a of invalidScripts) {
assert.throws(()=>eval(a[0]), SyntaxError, a[1]);
}
}
},
{
name: "Invalid hashbang in indirect eval",
body: function () {
let _eval = eval;
for (a of invalidScripts) {
assert.throws(()=>_eval(a[0]), SyntaxError, a[1]);
}
}
},
];

testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });
6 changes: 6 additions & 0 deletions test/Scanner/rlexe.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,10 @@
<baseline>InvalidCharacter.baseline</baseline>
</default>
</test>
<test>
<default>
<files>Hashbang.js</files>
<compile-flags>-args summary -endargs -ESHashbang</compile-flags>
</default>
</test>
</regress-exe>