Infer source type from filepath (#13772)

This commit is contained in:
fisker Cheung authored and GitHub committed 2022-11-02 19:00:08 +08:00
1 parent bf6cf1c96c
commit 6c688b7e28
9 files changed
+130 -27

No files matched your search

+10 -7
View File
@@ -3,15 +3,16 @@ import createError from "../../common/parser-create-error.js";
import tryCombinations from "../../utils/try-combinations.js";
import createParser from "./utils/create-parser.js";
import postprocess from "./postprocess/index.js";
import getSourceType from "./utils/get-source-type.js";
const require = createRequire(import.meta.url);
/** @type {import("acorn").Options} */
const parseOptions = {
ecmaVersion: "latest",
sourceType: "module",
// sourceType: "module",
allowReturnOutsideFunction: true,
allowImportExportEverywhere: true,
// allowImportExportEverywhere: true,
allowSuperOutsideMethod: true,
locations: true,
ranges: true,
@@ -55,6 +56,7 @@ function parseWithOptions(text, sourceType) {
const ast = parser.parse(text, {
...parseOptions,
sourceType,
allowImportExportEverywhere: sourceType === "module",
onComment: comments,
onToken: tokens,
});
@@ -65,14 +67,15 @@ function parseWithOptions(text, sourceType) {
}
function parse(text, options = {}) {
const { result: ast, error: moduleParseError } = tryCombinations(
() => parseWithOptions(text, /* sourceType */ "module"),
() => parseWithOptions(text, /* sourceType */ "script")
const sourceType = getSourceType(options);
const combinations = (sourceType ? [sourceType] : ["module", "script"]).map(
(sourceType) => () => parseWithOptions(text, sourceType)
);
const { result: ast, error } = tryCombinations(combinations);
if (!ast) {
// throw the error for `module` parsing
throw createParseError(moduleParseError);
throw createParseError(error);
}
options.originalText = text;
+4 -2
View File
@@ -6,6 +6,7 @@ import jsonParsers from "../../language-json/parser-json.js";
import createParser from "./utils/create-parser.js";
import createBabelParseError from "./utils/create-babel-parse-error.js";
import postprocess from "./postprocess/index.js";
import getSourceType from "./utils/get-source-type.js";
/**
* @typedef {import("@babel/parser").parse | import("@babel/parser").parseExpression} Parse
@@ -106,7 +107,8 @@ function createParse(parseMethod, ...optionsCombinations) {
}
let combinations = optionsCombinations;
if (opts.__babelSourceType === "script") {
const sourceType = opts.__babelSourceType ?? getSourceType(opts);
if (sourceType === "script") {
combinations = combinations.map((options) => ({
...options,
sourceType: "script",
@@ -143,7 +145,7 @@ function createParse(parseMethod, ...optionsCombinations) {
const parseFunction =
parseMethod === "parseExpression" ? parseExpression : babelParse;
const { result: ast, error } = tryCombinations(
...combinations.map(
combinations.map(
(options) => () => parseWithOptions(parseFunction, text, options)
)
);
+8 -5
View File
@@ -3,6 +3,7 @@ import createError from "../../common/parser-create-error.js";
import tryCombinations from "../../utils/try-combinations.js";
import createParser from "./utils/create-parser.js";
import postprocess from "./postprocess/index.js";
import getSourceType from "./utils/get-source-type.js";
const require = createRequire(import.meta.url);
@@ -38,14 +39,16 @@ function createParseError(error) {
function parse(text, options = {}) {
const { parse: espreeParse } = require("espree");
const { result: ast, error: moduleParseError } = tryCombinations(
() => espreeParse(text, { ...parseOptions, sourceType: "module" }),
() => espreeParse(text, { ...parseOptions, sourceType: "script" })
const sourceType = getSourceType(options);
const combinations = (sourceType ? [sourceType] : ["module", "script"]).map(
(/** @type {"module"|"script"} */ sourceType) => () =>
espreeParse(text, { ...parseOptions, sourceType })
);
const { result: ast, error } = tryCombinations(combinations);
if (!ast) {
// throw the error for `module` parsing
throw createParseError(moduleParseError);
throw createParseError(error);
}
options.originalText = text;
+10 -8
View File
@@ -2,11 +2,12 @@ import createError from "../../common/parser-create-error.js";
import tryCombinations from "../../utils/try-combinations.js";
import createParser from "./utils/create-parser.js";
import postprocess from "./postprocess/index.js";
import getSourceType from "./utils/get-source-type.js";
// https://github.com/meriyah/meriyah/blob/4676f60b6c149d7082bde2c9147f9ae2359c8075/src/parser.ts#L185
const parseOptions = {
// Allow module code
module: true,
// module: true,
// Enable stage 3 support (ESNext)
next: true,
// Enable start and end offsets to each node
@@ -39,14 +40,14 @@ const parseOptions = {
uniqueKeyInPattern: false,
};
function parseWithOptions(parse, text, module) {
function parseWithOptions(parse, text, sourceType) {
const comments = [];
const tokens = [];
/** @type {any} */
const ast = parse(text, {
...parseOptions,
module,
module: sourceType === "module",
onComment: comments,
onToken: tokens,
});
@@ -86,14 +87,15 @@ function createParseError(error) {
async function parse(text, options = {}) {
const { parse } = await import("meriyah");
const { result: ast, error: moduleParseError } = tryCombinations(
() => parseWithOptions(parse, text, /* module */ true),
() => parseWithOptions(parse, text, /* module */ false)
const sourceType = getSourceType(options);
const combinations = (sourceType ? [sourceType] : ["module", "script"]).map(
(sourceType) => () => parseWithOptions(parse, text, sourceType)
);
const { result: ast, error } = tryCombinations(combinations);
if (!ast) {
// Throw the error for `module` parsing
throw createParseError(moduleParseError);
throw createParseError(error);
}
options.originalText = text;
+4 -4
View File
@@ -42,16 +42,16 @@ async function parse(text, options = {}) {
const { parseWithNodeMaps } = await import(
"@typescript-eslint/typescript-estree/dist/parser.js"
);
const { result, error: firstError } = tryCombinations(
const { result, error } = tryCombinations([
// Try passing with our best guess first.
() => parseWithNodeMaps(textToParse, { ...parseOptions, jsx }),
// But if we get it wrong, try the opposite.
() => parseWithNodeMaps(textToParse, { ...parseOptions, jsx: !jsx })
);
() => parseWithNodeMaps(textToParse, { ...parseOptions, jsx: !jsx }),
]);
if (!result) {
// Suppose our guess is correct, throw the first error
throw createParseError(firstError);
throw createParseError(error);
}
options.originalText = text;
@@ -0,0 +1,18 @@
/** @returns {"module" | "script" | undefined} */
function getSourceType(options) {
let { filepath } = options;
if (!filepath) {
return;
}
filepath = filepath.toLowerCase();
if (filepath.endsWith(".cjs")) {
return "script";
}
if (filepath.endsWith(".mjs")) {
return "module";
}
}
export default getSourceType;
+1 -1
View File
@@ -1,4 +1,4 @@
function tryCombinations(...combinations) {
function tryCombinations(combinations) {
let firstError;
for (const [index, fn] of combinations.entries()) {
try {
@@ -0,0 +1,55 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`snippet: #0 [acorn] format 1`] = `
"'import' and 'export' may appear only with 'sourceType: module' (1:1)
> 1 | import "foo"
| ^"
`;
exports[`snippet: #0 [espree] format 1`] = `
"'import' and 'export' may appear only with 'sourceType: module' (1:1)
> 1 | import "foo"
| ^"
`;
exports[`snippet: #0 [meriyah] format 1`] = `
"The import keyword can only be used with the module goal (1:12)
> 1 | import "foo"
| ^"
`;
exports[`snippet: #1 [acorn] format 1`] = `
"'with' in strict mode (1:1)
> 1 | with(foo) {}
| ^"
`;
exports[`snippet: #1 [espree] format 1`] = `
"'with' in strict mode (1:1)
> 1 | with(foo) {}
| ^"
`;
exports[`snippet: #1 [meriyah] format 1`] = `
"Strict mode code may not include a with statement (1:5)
> 1 | with(foo) {}
| ^"
`;
exports[`snippet: #2 [acorn] format 1`] = `
"Deleting local variable in strict mode (1:1)
> 1 | delete foo
| ^"
`;
exports[`snippet: #2 [espree] format 1`] = `
"Deleting local variable in strict mode (1:1)
> 1 | delete foo
| ^"
`;
exports[`snippet: #2 [meriyah] format 1`] = `
"Calling delete on expression not allowed in strict mode (1:10)
> 1 | delete foo
| ^"
`;
@@ -0,0 +1,20 @@
run_spec(
{
importMeta: import.meta,
snippets: [
{
code: 'import "foo"',
filename: "script.cjs",
},
{
code: "with(foo) {}",
filename: "module.mjs",
},
{
code: "delete foo",
filename: "module.mJs",
},
],
},
["acorn", "espree", "meriyah"]
);