* parser: Add caret as topic reference (tests) * parser: Add caret as topic reference (implement) * generator: Avoid reconstructing validTopicTokenSet * babel-parser: Remove redundant throws in expression.js * Minimize diff * Update error message Co-authored-by: Nicolò Ribaudo <nicolo.ribaudo@gmail.com>
253 lines
6.6 KiB
TypeScript
253 lines
6.6 KiB
TypeScript
import SourceMap from "./source-map";
|
||
import Printer from "./printer";
|
||
import type * as t from "@babel/types";
|
||
|
||
import type { Format } from "./printer";
|
||
|
||
/**
|
||
* Babel's code generator, turns an ast into code, maintaining sourcemaps,
|
||
* user preferences, and valid output.
|
||
*/
|
||
|
||
class Generator extends Printer {
|
||
constructor(ast: t.Node, opts: { sourceMaps?: boolean } = {}, code) {
|
||
const format = normalizeOptions(code, opts);
|
||
const map = opts.sourceMaps ? new SourceMap(opts, code) : null;
|
||
super(format, map);
|
||
|
||
this.ast = ast;
|
||
}
|
||
|
||
ast: t.Node;
|
||
|
||
/**
|
||
* Generate code and sourcemap from ast.
|
||
*
|
||
* Appends comments that weren't attached to any node to the end of the generated output.
|
||
*/
|
||
|
||
generate() {
|
||
return super.generate(this.ast);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Normalize generator options, setting defaults.
|
||
*
|
||
* - Detects code indentation.
|
||
* - If `opts.compact = "auto"` and the code is over 500KB, `compact` will be set to `true`.
|
||
*/
|
||
|
||
function normalizeOptions(code, opts): Format {
|
||
const format: Format = {
|
||
auxiliaryCommentBefore: opts.auxiliaryCommentBefore,
|
||
auxiliaryCommentAfter: opts.auxiliaryCommentAfter,
|
||
shouldPrintComment: opts.shouldPrintComment,
|
||
retainLines: opts.retainLines,
|
||
retainFunctionParens: opts.retainFunctionParens,
|
||
comments: opts.comments == null || opts.comments,
|
||
compact: opts.compact,
|
||
minified: opts.minified,
|
||
concise: opts.concise,
|
||
indent: {
|
||
adjustMultilineComment: true,
|
||
style: " ",
|
||
base: 0,
|
||
},
|
||
decoratorsBeforeExport: !!opts.decoratorsBeforeExport,
|
||
jsescOption: {
|
||
quotes: "double",
|
||
wrap: true,
|
||
minimal: process.env.BABEL_8_BREAKING ? true : false,
|
||
...opts.jsescOption,
|
||
},
|
||
recordAndTupleSyntaxType: opts.recordAndTupleSyntaxType,
|
||
topicToken: opts.topicToken,
|
||
};
|
||
|
||
if (!process.env.BABEL_8_BREAKING) {
|
||
format.jsonCompatibleStrings = opts.jsonCompatibleStrings;
|
||
}
|
||
|
||
if (format.minified) {
|
||
format.compact = true;
|
||
|
||
format.shouldPrintComment =
|
||
format.shouldPrintComment || (() => format.comments);
|
||
} else {
|
||
format.shouldPrintComment =
|
||
format.shouldPrintComment ||
|
||
(value =>
|
||
format.comments ||
|
||
value.indexOf("@license") >= 0 ||
|
||
value.indexOf("@preserve") >= 0);
|
||
}
|
||
|
||
if (format.compact === "auto") {
|
||
format.compact = code.length > 500_000; // 500KB
|
||
|
||
if (format.compact) {
|
||
console.error(
|
||
"[BABEL] Note: The code generator has deoptimised the styling of " +
|
||
`${opts.filename} as it exceeds the max of ${"500KB"}.`,
|
||
);
|
||
}
|
||
}
|
||
|
||
if (format.compact) {
|
||
format.indent.adjustMultilineComment = false;
|
||
}
|
||
|
||
return format;
|
||
}
|
||
|
||
export interface GeneratorOptions {
|
||
/**
|
||
* Optional string to add as a block comment at the start of the output file.
|
||
*/
|
||
auxiliaryCommentBefore?: string;
|
||
|
||
/**
|
||
* Optional string to add as a block comment at the end of the output file.
|
||
*/
|
||
auxiliaryCommentAfter?: string;
|
||
|
||
/**
|
||
* Function that takes a comment (as a string) and returns true if the comment should be included in the output.
|
||
* By default, comments are included if `opts.comments` is `true` or if `opts.minifed` is `false` and the comment
|
||
* contains `@preserve` or `@license`.
|
||
*/
|
||
shouldPrintComment?(comment: string): boolean;
|
||
|
||
/**
|
||
* Attempt to use the same line numbers in the output code as in the source code (helps preserve stack traces).
|
||
* Defaults to `false`.
|
||
*/
|
||
retainLines?: boolean;
|
||
|
||
/**
|
||
* Retain parens around function expressions (could be used to change engine parsing behavior)
|
||
* Defaults to `false`.
|
||
*/
|
||
retainFunctionParens?: boolean;
|
||
|
||
/**
|
||
* Should comments be included in output? Defaults to `true`.
|
||
*/
|
||
comments?: boolean;
|
||
|
||
/**
|
||
* Set to true to avoid adding whitespace for formatting. Defaults to the value of `opts.minified`.
|
||
*/
|
||
compact?: boolean | "auto";
|
||
|
||
/**
|
||
* Should the output be minified. Defaults to `false`.
|
||
*/
|
||
minified?: boolean;
|
||
|
||
/**
|
||
* Set to true to reduce whitespace (but not as much as opts.compact). Defaults to `false`.
|
||
*/
|
||
concise?: boolean;
|
||
|
||
/**
|
||
* Used in warning messages
|
||
*/
|
||
filename?: string;
|
||
|
||
/**
|
||
* Enable generating source maps. Defaults to `false`.
|
||
*/
|
||
sourceMaps?: boolean;
|
||
|
||
/**
|
||
* A root for all relative URLs in the source map.
|
||
*/
|
||
sourceRoot?: string;
|
||
|
||
/**
|
||
* The filename for the source code (i.e. the code in the `code` argument).
|
||
* This will only be used if `code` is a string.
|
||
*/
|
||
sourceFileName?: string;
|
||
|
||
/**
|
||
* Set to true to run jsesc with "json": true to print "\u00A9" vs. "©";
|
||
*/
|
||
jsonCompatibleStrings?: boolean;
|
||
|
||
/**
|
||
* Set to true to enable support for experimental decorators syntax before module exports.
|
||
* Defaults to `false`.
|
||
*/
|
||
decoratorsBeforeExport?: boolean;
|
||
|
||
/**
|
||
* Options for outputting jsesc representation.
|
||
*/
|
||
jsescOption?: {
|
||
/**
|
||
* The type of quote to use in the output. If omitted, autodetects based on `ast.tokens`.
|
||
*/
|
||
quotes?: "single" | "double";
|
||
|
||
/**
|
||
* When enabled, the output is a valid JavaScript string literal wrapped in quotes. The type of quotes can be specified through the quotes setting.
|
||
* Defaults to `true`.
|
||
*/
|
||
wrap?: boolean;
|
||
};
|
||
|
||
/**
|
||
* For use with the Hack-style pipe operator.
|
||
* Changes what token is used for pipe bodies’ topic references.
|
||
*/
|
||
topicToken?: "^" | "%" | "#";
|
||
}
|
||
|
||
export interface GeneratorResult {
|
||
code: string;
|
||
map: {
|
||
version: number;
|
||
sources: string[];
|
||
names: string[];
|
||
sourceRoot?: string;
|
||
sourcesContent?: string[];
|
||
mappings: string;
|
||
file: string;
|
||
} | null;
|
||
}
|
||
|
||
/**
|
||
* We originally exported the Generator class above, but to make it extra clear that it is a private API,
|
||
* we have moved that to an internal class instance and simplified the interface to the two public methods
|
||
* that we wish to support.
|
||
*/
|
||
|
||
export class CodeGenerator {
|
||
private _generator: Generator;
|
||
constructor(ast: t.Node, opts?: GeneratorOptions, code?: string) {
|
||
this._generator = new Generator(ast, opts, code);
|
||
}
|
||
generate(): GeneratorResult {
|
||
return this._generator.generate();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Turns an AST into code, maintaining sourcemaps, user preferences, and valid output.
|
||
* @param ast - the abstract syntax tree from which to generate output code.
|
||
* @param opts - used for specifying options for code generation.
|
||
* @param code - the original source code, used for source maps.
|
||
* @returns - an object containing the output code and source map.
|
||
*/
|
||
export default function generate(
|
||
ast: t.Node,
|
||
opts?: GeneratorOptions,
|
||
code?: string | { [filename: string]: string },
|
||
): any {
|
||
const gen = new Generator(ast, opts, code);
|
||
return gen.generate();
|
||
}
|