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"); 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 // Handle nodes with decorators. They should start at the first decorator
if ( if (
!opts.ignoreDecorators &&
node.declaration && node.declaration &&
node.declaration.decorators && node.declaration.decorators &&
node.declaration.decorators.length > 0 node.declaration.decorators.length > 0
) { ) {
return locStart(node.declaration.decorators[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]); return locStart(node.decorators[0]);
} }

View File

@ -4,71 +4,93 @@ const createError = require("../common/parser-create-error");
const hasPragma = require("./pragma").hasPragma; const hasPragma = require("./pragma").hasPragma;
const locFns = require("./loc"); 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) { function parse(text, parsers, opts) {
// Inline the require to avoid loading all the JS if we don't use it // Inline the require to avoid loading all the JS if we don't use it
const babylon = require("@babel/parser"); const babylon = require("@babel/parser");
const babylonOptions = { const combinations = [
sourceType: "module", babylonOptions({ strictMode: true }, ["decorators-legacy"]),
allowAwaitOutsideFunction: true, babylonOptions({ strictMode: false }, ["decorators-legacy"]),
allowImportExportEverywhere: true, babylonOptions({ strictMode: true }, [
allowReturnOutsideFunction: true, ["decorators", { decoratorsBeforeExport: false }]
allowSuperOutsideMethod: true, ]),
plugins: [ babylonOptions({ strictMode: false }, [
"jsx", ["decorators", { decoratorsBeforeExport: false }]
"flow", ])
"doExpressions", ];
"objectRestSpread",
"decorators-legacy",
"classProperties",
"exportDefaultFrom",
"exportNamespaceFrom",
"asyncGenerators",
"functionBind",
"functionSent",
"dynamicImport",
"numericSeparator",
"importMeta",
"optionalCatchBinding",
"optionalChaining",
"classPrivateProperties",
["pipelineOperator", { proposal: "minimal" }],
"nullishCoalescingOperator",
"bigInt",
"throwExpressions"
]
};
const parseMethod = const parseMethod =
!opts || opts.parser === "babylon" ? "parse" : "parseExpression"; !opts || opts.parser === "babylon" ? "parse" : "parseExpression";
let ast; let ast;
try { try {
ast = babylon[parseMethod](text, babylonOptions); ast = tryCombinations(babylon[parseMethod].bind(null, text), combinations);
} catch (originalError) { } catch (error) {
try { throw createError(
ast = babylon[parseMethod]( // babel error prints (l:c) with cols that are zero indexed
text, // so we need our custom error
Object.assign({}, babylonOptions, { strictMode: false }) error.message.replace(/ \(.*\)/, ""),
); {
} catch (nonStrictError) { start: {
throw createError( line: error.loc.line,
// babel error prints (l:c) with cols that are zero indexed column: error.loc.column + 1
// so we need our custom error
originalError.message.replace(/ \(.*\)/, ""),
{
start: {
line: originalError.loc.line,
column: originalError.loc.column + 1
}
} }
); }
} );
} }
delete ast.tokens; delete ast.tokens;
return ast; 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) { function parseJson(text, parsers, opts) {
const ast = parse(text, parsers, Object.assign({}, opts, { parser: "json" })); const ast = parse(text, parsers, Object.assign({}, opts, { parser: "json" }));

View File

@ -89,13 +89,19 @@ function genericPrint(path, options, printPath, args) {
return linesWithoutParens; return linesWithoutParens;
} }
const parentExportDecl = getParentExportDeclaration(path);
const decorators = []; const decorators = [];
if ( if (
node.decorators && node.decorators &&
node.decorators.length > 0 && node.decorators.length > 0 &&
// If the parent node is an export declaration, it will be // If the parent node is an export declaration and the decorator
// responsible for printing node.decorators. // was written before the export, the export will be responsible
!getParentExportDeclaration(path) // for printing the decorators.
!(
parentExportDecl &&
options.locStart(parentExportDecl, { ignoreDecorators: true }) >
options.locStart(node.decorators[0])
)
) { ) {
const shouldBreak = const shouldBreak =
node.type === "ClassDeclaration" || node.type === "ClassDeclaration" ||
@ -121,10 +127,19 @@ function genericPrint(path, options, printPath, args) {
decorators.push(printPath(decoratorPath), separator); decorators.push(printPath(decoratorPath), separator);
}, "decorators"); }, "decorators");
if (parentExportDecl) {
decorators.unshift(hardline);
}
} else if ( } else if (
isExportDeclaration(node) && isExportDeclaration(node) &&
node.declaration && 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 // Export declarations are responsible for printing any decorators
// that logically apply to node.declaration. // 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"]);