Skip to content
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
2 changes: 1 addition & 1 deletion acorn-loose/src/statement.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ lp.parseTopLevel = function() {
while (this.tok.type !== tt.eof) node.body.push(this.parseStatement())
this.toks.adaptDirectivePrologue(node.body)
this.last = this.tok
node.sourceType = this.options.sourceType
node.sourceType = this.options.sourceType === "commonjs" ? "script" : this.options.sourceType
return this.finishNode(node, "Program")
}

Expand Down
7 changes: 4 additions & 3 deletions acorn/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,12 @@ required):
implemented through plugins.

- **sourceType**: Indicate the mode the code should be parsed in. Can be
either `"script"` or `"module"`. This influences global strict mode
either `"script"`, `"module"` or `"commonjs"`. This influences global strict mode
and parsing of `import` and `export` declarations.

**NOTE**: If set to `"module"`, then static `import` / `export` syntax
will be valid, even if `ecmaVersion` is less than 6.
will be valid, even if `ecmaVersion` is less than 6. If set to `"commonjs"`,
it is the same as `"script"` except that the top-level scope behaves like a function.

- **onInsertedSemicolon**: If given a callback, that callback will be
called whenever a missing semicolon is inserted by the parser. The
Expand Down Expand Up @@ -97,7 +98,7 @@ required):
for `ecmaVersion` 2022 and later, `false` for lower versions.
Setting this option to `true` allows to have top-level `await`
expressions. They are still not allowed in non-`async` functions,
though.
though. Setting this option to `true` is not allowed when `sourceType: "commonjs"`.

- **allowSuperOutsideMethod**: By default, `super` outside a method
raises an error. Set this to `true` to accept such code.
Expand Down
4 changes: 2 additions & 2 deletions acorn/src/acorn.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -614,10 +614,10 @@ export interface Options {

/**
* `sourceType` indicates the mode the code should be parsed in.
* Can be either `"script"` or `"module"`. This influences global
* Can be either `"script"`, `"module"` or `"commonjs"`. This influences global
* strict mode and parsing of `import` and `export` declarations.
*/
sourceType?: "script" | "module"
sourceType?: "script" | "module" | "commonjs"

/**
* a callback that will be called when a semicolon is automatically inserted.
Expand Down
5 changes: 4 additions & 1 deletion acorn/src/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const defaultOptions = {
// for new syntax features.
ecmaVersion: null,
// `sourceType` indicates the mode the code should be parsed in.
// Can be either `"script"` or `"module"`. This influences global
// Can be either `"script"`, `"module"` or `"commonjs"`. This influences global
// strict mode and parsing of `import` and `export` declarations.
sourceType: "script",
// `onInsertedSemicolon` can be a callback that will be called when
Expand Down Expand Up @@ -137,6 +137,9 @@ export function getOptions(opts) {
if (isArray(options.onComment))
options.onComment = pushComment(options, options.onComment)

if (options.sourceType === "commonjs" && options.allowAwaitOutsideFunction)
throw new Error("Cannot use allowAwaitOutsideFunction with sourceType: commonjs")

Copy link
Contributor

Choose a reason for hiding this comment

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

Should we also throw if allowReturnOutsideFunction: false is used with sourceType: "commonjs"?

return options
}

Expand Down
7 changes: 6 additions & 1 deletion acorn/src/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,12 @@ export class Parser {

// Scope tracking for duplicate variable names (see scope.js)
this.scopeStack = []
this.enterScope(SCOPE_TOP)
this.enterScope(
this.options.sourceType === "commonjs"
// In commonjs, the top-level scope behaves like a function scope
? SCOPE_FUNCTION
: SCOPE_TOP
)

// For RegExp validation
this.regexpState = null
Expand Down
2 changes: 1 addition & 1 deletion acorn/src/statement.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pp.parseTopLevel = function(node) {
this.raiseRecoverable(this.undefinedExports[name].start, `Export '${name}' is not defined`)
this.adaptDirectivePrologue(node.body)
this.next()
node.sourceType = this.options.sourceType
node.sourceType = this.options.sourceType === "commonjs" ? "script" : this.options.sourceType
return this.finishNode(node, "Program")
}

Expand Down
2 changes: 1 addition & 1 deletion test/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ exports.runTests = function(config, callback) {
try {
var ast = parse(test.code, testOpts);
} catch(e) {
if (!(e instanceof SyntaxError)) { console.log(e.stack); throw e; }
if (test.error) {
if (test.error.charAt(0) === "~" ? e.message.indexOf(test.error.slice(1)) > -1 : e.message === test.error)
callback("ok", test.code);
else
callback("fail", test.code, "Expected error message: " + test.error + "\nGot error message: " + e.message);
} else {
if (!(e instanceof SyntaxError)) { console.log(e.stack); throw e; }
callback("error", test.code, e.message || e.toString());
}
continue
Expand Down
23 changes: 23 additions & 0 deletions test/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
require("./tests-module-string-names.js");
require("./tests-import-attributes.js");
require("./tests-using.js");
require("./tests-commonjs.js");
var acorn = require("../acorn")
var acorn_loose = require("../acorn-loose")

Expand Down Expand Up @@ -84,6 +85,28 @@
return opts.loose !== false;
}
}
},

// Test whether the test for `sourceType: 'script'` produces the same result for `'commonjs'`.
'Normal with sourceType: commonjs': {
config: {
parse: (code, option) => acorn.parse(code, Object.assign({}, option, { sourceType: 'commonjs' })),
filter: function (test) {
var opts = test.options || {};
return opts.commonjs !== false && !opts.allowAwaitOutsideFunction && (!opts.sourceType || opts.sourceType === 'script');
}
}
},
'Loose with sourceType: commonjs': {
config: {
parse: (code, option) => acorn_loose.parse(code, Object.assign({}, option, { sourceType: 'commonjs' })),
loose: true,
filter: function (test) {
var opts = test.options || {};
if (opts.loose === false) return false;
return opts.commonjs !== false && !opts.allowAwaitOutsideFunction && (!opts.sourceType || opts.sourceType === 'script');
}
}
}
};

Expand Down
97 changes: 97 additions & 0 deletions test/tests-commonjs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
if (typeof exports !== "undefined") {
var test = require("./driver.js").test
var testFail = require("./driver.js").testFail
}

// Top-level using declaration with commonjs
test("using x = resource;", {
type: "Program",
body: [{
type: "VariableDeclaration",
declarations: [{
type: "VariableDeclarator",
id: {
type: "Identifier",
name: "x"
},
init: {
type: "Identifier",
name: "resource"
}
}],
kind: "using"
}],
sourceType: "script"
}, {ecmaVersion: 17, sourceType: "commonjs"});

// Top-level new.target with commonjs
test("new.target", {
type: "Program",
body: [{
type: "ExpressionStatement",
expression: {
type: "MetaProperty",
meta: {type: "Identifier", name: "new"},
property: {type: "Identifier", name: "target"}
}
}],
sourceType: "script"
}, {ecmaVersion: 6, sourceType: "commonjs"});

// new.target at top-level scope with commonjs
test("let y = () => new.target", {
type: "Program",
body: [{
type: "VariableDeclaration",
declarations: [{
type: "VariableDeclarator",
id: {
type: "Identifier",
name: "y"
},
init: {
type: "ArrowFunctionExpression",
body: {
type: "MetaProperty",
meta: {type: "Identifier", name: "new"},
property: {type: "Identifier", name: "target"}
},
params: []
}
}],
kind: "let"
}],
sourceType: "script"
}, {ecmaVersion: 6, sourceType: "commonjs"});

// Top-level return statement with commonjs
test("return {} / 2", {
type: "Program",
body: [{
type: "ReturnStatement",
argument: {
type: "BinaryExpression",
left: {
type: "ObjectExpression",
properties: []
},
right: {
type: "Literal",
value: 2
},
operator: "/"
}
}],
sourceType: "script"
}, {sourceType: "commonjs"});

// Illegal return statement with commonjs
testFail(`class X { static { return; } }`, "'return' outside of function (1:19)", {ecmaVersion: 13, sourceType: "commonjs"});

// Top-level await using declaration with commonjs
testFail("await using x = resource;", "Await using cannot appear outside of async function (1:0)", {ecmaVersion: 17, sourceType: "commonjs"});

// Disallowing allowAwaitOutsideFunction with commonjs
testFail("await 1", "Cannot use allowAwaitOutsideFunction with sourceType: commonjs", {allowAwaitOutsideFunction: true, sourceType: "commonjs"});
testFail("x", "Cannot use allowAwaitOutsideFunction with sourceType: commonjs", {allowAwaitOutsideFunction: true, sourceType: "commonjs"});
test("x", {}, {allowAwaitOutsideFunction: false, sourceType: "commonjs"});
4 changes: 2 additions & 2 deletions test/tests-harmony.js
Original file line number Diff line number Diff line change
Expand Up @@ -14873,9 +14873,9 @@ test("function foo() { new.target; }", {
}, {ecmaVersion: 6});

testFail("new.prop", "The only valid meta property for new is 'new.target' (1:4)", {ecmaVersion: 6});
testFail("new.target", "'new.target' can only be used in functions and class static block (1:0)", {ecmaVersion: 6});
testFail("new.target", "'new.target' can only be used in functions and class static block (1:0)", {ecmaVersion: 6, commonjs: false});
test("function x() { return () => new.target }", {}, {ecmaVersion: 6});
testFail("let y = () => new.target", "'new.target' can only be used in functions and class static block (1:14)", {ecmaVersion: 6});
testFail("let y = () => new.target", "'new.target' can only be used in functions and class static block (1:14)", {ecmaVersion: 6, commonjs: false});

test("export default function foo() {} false", {
body: [
Expand Down
4 changes: 2 additions & 2 deletions test/tests-using.js
Original file line number Diff line number Diff line change
Expand Up @@ -1102,14 +1102,14 @@ testFail("let await using x = resource;", "Cannot use keyword 'await' outside an
// let using is not allowed
testFail("let using x = resource;", "Unexpected token (1:10)", {ecmaVersion: 17, sourceType: "module"});
// top level using is not allowed
testFail("using x = resource;", "Using declaration cannot appear in the top level when source type is `script` or in the bare case statement (1:0)", {ecmaVersion: 17, sourceType: "script"});
testFail("using x = resource;", "Using declaration cannot appear in the top level when source type is `script` or in the bare case statement (1:0)", {ecmaVersion: 17, sourceType: "script", commonjs: false});

// BoundNames contains "let"
testFail("async function test() { await using let = resource; }", "The keyword 'let' is reserved (1:36)", {ecmaVersion: 17, sourceType: "module"});
// BoundNames contains duplicate entries
testFail("async function test() { await using x = resource1, x = resource2; }", "Identifier 'x' has already been declared (1:51)", {ecmaVersion: 17, sourceType: "module"});
// top level await using is not allowed
testFail("await using x = resource;", "Using declaration cannot appear in the top level when source type is `script` or in the bare case statement (1:0)", {ecmaVersion: 17, sourceType: "script"});
testFail("await using x = resource;", "Using declaration cannot appear in the top level when source type is `script` or in the bare case statement (1:0)", {ecmaVersion: 17, sourceType: "script", commonjs: false});

// Basic missing initializer
testFail("{ using x; }", "Missing initializer in using declaration (1:9)", {ecmaVersion: 17, sourceType: "module"});
Expand Down
2 changes: 1 addition & 1 deletion test/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -27458,7 +27458,7 @@ testFail("\"\\u",
"Bad character escape sequence (1:3)");

testFail("return",
"'return' outside of function (1:0)");
"'return' outside of function (1:0)", { commonjs: false });

testFail("break",
"Unsyntactic break (1:0)");
Expand Down