Respect original text decorator order (#5207)

master
Lucas Duailibe 2018-10-17 14:14:59 -03:00 committed by GitHub
parent 023a8b78df
commit 62e4654e60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 135 additions and 55 deletions

View File

@ -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]);
}

View File

@ -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" }));

View File

@ -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.

View File

@ -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 {}
`;

View File

@ -0,0 +1,3 @@
export @decorator class Foo {}
export default @decorator class {}

View File

@ -0,0 +1,5 @@
@decorator
export class Foo {}
@decorator
export default class {}

View File

@ -0,0 +1,2 @@
// TypeScript and Flow don't accept decorator after export
run_spec(__dirname, ["babylon"]);