Respect original text decorator order (#5207)
parent
023a8b78df
commit
62e4654e60
|
@ -2,16 +2,18 @@
|
|||
|
||||
const getLast = require("../utils/get-last");
|
||||
|
||||
function locStart(node) {
|
||||
function locStart(node, opts) {
|
||||
opts = opts || {};
|
||||
// Handle nodes with decorators. They should start at the first decorator
|
||||
if (
|
||||
!opts.ignoreDecorators &&
|
||||
node.declaration &&
|
||||
node.declaration.decorators &&
|
||||
node.declaration.decorators.length > 0
|
||||
) {
|
||||
return locStart(node.declaration.decorators[0]);
|
||||
}
|
||||
if (node.decorators && node.decorators.length > 0) {
|
||||
if (!opts.ignoreDecorators && node.decorators && node.decorators.length > 0) {
|
||||
return locStart(node.decorators[0]);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,71 +4,93 @@ const createError = require("../common/parser-create-error");
|
|||
const hasPragma = require("./pragma").hasPragma;
|
||||
const locFns = require("./loc");
|
||||
|
||||
function babylonOptions(extraOptions, extraPlugins) {
|
||||
return Object.assign(
|
||||
{
|
||||
sourceType: "module",
|
||||
allowAwaitOutsideFunction: true,
|
||||
allowImportExportEverywhere: true,
|
||||
allowReturnOutsideFunction: true,
|
||||
allowSuperOutsideMethod: true,
|
||||
plugins: [
|
||||
"jsx",
|
||||
"flow",
|
||||
"doExpressions",
|
||||
"objectRestSpread",
|
||||
"classProperties",
|
||||
"exportDefaultFrom",
|
||||
"exportNamespaceFrom",
|
||||
"asyncGenerators",
|
||||
"functionBind",
|
||||
"functionSent",
|
||||
"dynamicImport",
|
||||
"numericSeparator",
|
||||
"importMeta",
|
||||
"optionalCatchBinding",
|
||||
"optionalChaining",
|
||||
"classPrivateProperties",
|
||||
["pipelineOperator", { proposal: "minimal" }],
|
||||
"nullishCoalescingOperator",
|
||||
"bigInt",
|
||||
"throwExpressions"
|
||||
].concat(extraPlugins)
|
||||
},
|
||||
extraOptions
|
||||
);
|
||||
}
|
||||
|
||||
function parse(text, parsers, opts) {
|
||||
// Inline the require to avoid loading all the JS if we don't use it
|
||||
const babylon = require("@babel/parser");
|
||||
|
||||
const babylonOptions = {
|
||||
sourceType: "module",
|
||||
allowAwaitOutsideFunction: true,
|
||||
allowImportExportEverywhere: true,
|
||||
allowReturnOutsideFunction: true,
|
||||
allowSuperOutsideMethod: true,
|
||||
plugins: [
|
||||
"jsx",
|
||||
"flow",
|
||||
"doExpressions",
|
||||
"objectRestSpread",
|
||||
"decorators-legacy",
|
||||
"classProperties",
|
||||
"exportDefaultFrom",
|
||||
"exportNamespaceFrom",
|
||||
"asyncGenerators",
|
||||
"functionBind",
|
||||
"functionSent",
|
||||
"dynamicImport",
|
||||
"numericSeparator",
|
||||
"importMeta",
|
||||
"optionalCatchBinding",
|
||||
"optionalChaining",
|
||||
"classPrivateProperties",
|
||||
["pipelineOperator", { proposal: "minimal" }],
|
||||
"nullishCoalescingOperator",
|
||||
"bigInt",
|
||||
"throwExpressions"
|
||||
]
|
||||
};
|
||||
const combinations = [
|
||||
babylonOptions({ strictMode: true }, ["decorators-legacy"]),
|
||||
babylonOptions({ strictMode: false }, ["decorators-legacy"]),
|
||||
babylonOptions({ strictMode: true }, [
|
||||
["decorators", { decoratorsBeforeExport: false }]
|
||||
]),
|
||||
babylonOptions({ strictMode: false }, [
|
||||
["decorators", { decoratorsBeforeExport: false }]
|
||||
])
|
||||
];
|
||||
|
||||
const parseMethod =
|
||||
!opts || opts.parser === "babylon" ? "parse" : "parseExpression";
|
||||
|
||||
let ast;
|
||||
try {
|
||||
ast = babylon[parseMethod](text, babylonOptions);
|
||||
} catch (originalError) {
|
||||
try {
|
||||
ast = babylon[parseMethod](
|
||||
text,
|
||||
Object.assign({}, babylonOptions, { strictMode: false })
|
||||
);
|
||||
} catch (nonStrictError) {
|
||||
throw createError(
|
||||
// babel error prints (l:c) with cols that are zero indexed
|
||||
// so we need our custom error
|
||||
originalError.message.replace(/ \(.*\)/, ""),
|
||||
{
|
||||
start: {
|
||||
line: originalError.loc.line,
|
||||
column: originalError.loc.column + 1
|
||||
}
|
||||
ast = tryCombinations(babylon[parseMethod].bind(null, text), combinations);
|
||||
} catch (error) {
|
||||
throw createError(
|
||||
// babel error prints (l:c) with cols that are zero indexed
|
||||
// so we need our custom error
|
||||
error.message.replace(/ \(.*\)/, ""),
|
||||
{
|
||||
start: {
|
||||
line: error.loc.line,
|
||||
column: error.loc.column + 1
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
delete ast.tokens;
|
||||
return ast;
|
||||
}
|
||||
|
||||
function tryCombinations(fn, combinations) {
|
||||
let error;
|
||||
for (let i = 0; i < combinations.length; i++) {
|
||||
try {
|
||||
return fn(combinations[i]);
|
||||
} catch (_error) {
|
||||
if (!error) {
|
||||
error = _error;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
function parseJson(text, parsers, opts) {
|
||||
const ast = parse(text, parsers, Object.assign({}, opts, { parser: "json" }));
|
||||
|
||||
|
|
|
@ -89,13 +89,19 @@ function genericPrint(path, options, printPath, args) {
|
|||
return linesWithoutParens;
|
||||
}
|
||||
|
||||
const parentExportDecl = getParentExportDeclaration(path);
|
||||
const decorators = [];
|
||||
if (
|
||||
node.decorators &&
|
||||
node.decorators.length > 0 &&
|
||||
// If the parent node is an export declaration, it will be
|
||||
// responsible for printing node.decorators.
|
||||
!getParentExportDeclaration(path)
|
||||
// If the parent node is an export declaration and the decorator
|
||||
// was written before the export, the export will be responsible
|
||||
// for printing the decorators.
|
||||
!(
|
||||
parentExportDecl &&
|
||||
options.locStart(parentExportDecl, { ignoreDecorators: true }) >
|
||||
options.locStart(node.decorators[0])
|
||||
)
|
||||
) {
|
||||
const shouldBreak =
|
||||
node.type === "ClassDeclaration" ||
|
||||
|
@ -121,10 +127,19 @@ function genericPrint(path, options, printPath, args) {
|
|||
|
||||
decorators.push(printPath(decoratorPath), separator);
|
||||
}, "decorators");
|
||||
|
||||
if (parentExportDecl) {
|
||||
decorators.unshift(hardline);
|
||||
}
|
||||
} else if (
|
||||
isExportDeclaration(node) &&
|
||||
node.declaration &&
|
||||
node.declaration.decorators
|
||||
node.declaration.decorators &&
|
||||
node.declaration.decorators.length > 0 &&
|
||||
// Only print decorators here if they were written before the export,
|
||||
// otherwise they are printed by the node.declaration
|
||||
options.locStart(node, { ignoreDecorators: true }) >
|
||||
options.locStart(node.declaration.decorators[0])
|
||||
) {
|
||||
// Export declarations are responsible for printing any decorators
|
||||
// that logically apply to node.declaration.
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`after_export.js - babylon-verify 1`] = `
|
||||
export @decorator class Foo {}
|
||||
|
||||
export default @decorator class {}
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
export
|
||||
@decorator
|
||||
class Foo {}
|
||||
|
||||
export default
|
||||
@decorator
|
||||
class {}
|
||||
|
||||
`;
|
||||
|
||||
exports[`before_export.js - babylon-verify 1`] = `
|
||||
@decorator
|
||||
export class Foo {}
|
||||
|
||||
@decorator
|
||||
export default class {}
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@decorator
|
||||
export class Foo {}
|
||||
|
||||
@decorator
|
||||
export default class {}
|
||||
|
||||
`;
|
|
@ -0,0 +1,3 @@
|
|||
export @decorator class Foo {}
|
||||
|
||||
export default @decorator class {}
|
|
@ -0,0 +1,5 @@
|
|||
@decorator
|
||||
export class Foo {}
|
||||
|
||||
@decorator
|
||||
export default class {}
|
|
@ -0,0 +1,2 @@
|
|||
// TypeScript and Flow don't accept decorator after export
|
||||
run_spec(__dirname, ["babylon"]);
|
Loading…
Reference in New Issue