diff --git a/index.js b/index.js index ea23ffb0e..677e1c219 100644 --- a/index.js +++ b/index.js @@ -1,17 +1,22 @@ "use strict"; -const comments = require("./src/main/comments"); +const docblock = require("jest-docblock"); + const version = require("./package.json").version; -const printAstToDoc = require("./src/main/ast-to-doc"); + const util = require("./src/common/util"); +const getSupportInfo = require("./src/common/support").getSupportInfo; + +const comments = require("./src/main/comments"); +const printAstToDoc = require("./src/main/ast-to-doc"); +const normalizeOptions = require("./src/main/options").normalize; +const parser = require("./src/main/parser"); + +const config = require("./src/config/resolve-config"); + const doc = require("./src/doc"); const printDocToString = doc.printer.printDocToString; const printDocToDebug = doc.debug.printDocToDebug; -const normalizeOptions = require("./src/common/options").normalize; -const parser = require("./src/main/parser"); -const config = require("./src/config/resolve-config"); -const getSupportInfo = require("./src/common/support").getSupportInfo; -const docblock = require("jest-docblock"); function guessLineEnding(text) { const index = text.indexOf("\n"); @@ -101,7 +106,7 @@ function formatWithCursor(text, opts, addAlignmentSize) { let cursorOffset; if (opts.cursorOffset >= 0) { - const cursorNodeAndParents = findNodeAtOffset(ast, opts.cursorOffset); + const cursorNodeAndParents = findNodeAtOffset(ast, opts.cursorOffset, opts); const cursorNode = cursorNodeAndParents.node; if (cursorNode) { cursorOffset = opts.cursorOffset - util.locStart(cursorNode); @@ -179,16 +184,21 @@ function findSiblingAncestors(startNodeAndParents, endNodeAndParents) { }; } -function findNodeAtOffset(node, offset, predicate, parentNodes) { +function findNodeAtOffset(node, offset, options, predicate, parentNodes) { predicate = predicate || (() => true); parentNodes = parentNodes || []; const start = util.locStart(node); const end = util.locEnd(node); if (start <= offset && offset <= end) { - for (const childNode of comments.getSortedChildNodes(node)) { + for (const childNode of comments.getSortedChildNodes( + node, + undefined /* text */, + options + )) { const childResult = findNodeAtOffset( childNode, offset, + options, predicate, [node].concat(parentNodes) ); @@ -301,11 +311,17 @@ function calculateRange(text, opts, ast) { } } - const startNodeAndParents = findNodeAtOffset(ast, startNonWhitespace, node => - isSourceElement(opts, node) + const startNodeAndParents = findNodeAtOffset( + ast, + startNonWhitespace, + opts, + node => isSourceElement(opts, node) ); - const endNodeAndParents = findNodeAtOffset(ast, endNonWhitespace, node => - isSourceElement(opts, node) + const endNodeAndParents = findNodeAtOffset( + ast, + endNonWhitespace, + opts, + node => isSourceElement(opts, node) ); if (!startNodeAndParents || !endNodeAndParents) { @@ -398,6 +414,7 @@ module.exports = { /* istanbul ignore next */ __debug: { parse: function(text, opts) { + opts = normalizeOptions(opts); return parser.parse(text, opts); }, formatAST: function(ast, opts) { diff --git a/src/cli/util.js b/src/cli/util.js index 421a6834e..ec7df22c7 100644 --- a/src/cli/util.js +++ b/src/cli/util.js @@ -16,7 +16,7 @@ const cleanAST = require("../common/clean-ast").cleanAST; const resolver = require("../config/resolve-config"); const constant = require("./constant"); const validator = require("./validator"); -const apiDefaultOptions = require("../common/options").defaults; +const apiDefaultOptions = require("../main/options").defaults; const errors = require("../common/errors"); const logger = require("./logger"); const thirdParty = require("../common/third-party"); diff --git a/src/common/load-plugins.js b/src/common/load-plugins.js index 42ef58c27..038050cbf 100644 --- a/src/common/load-plugins.js +++ b/src/common/load-plugins.js @@ -12,9 +12,15 @@ function loadPlugins(options) { require("../language-markdown"), require("../language-html"), require("../language-vue") - ]; + ].filter(plugin => { + return options.plugins.indexOf(plugin) < 0; + }); const externalPlugins = options.plugins.map(plugin => { + if (typeof plugin !== "string") { + return plugin; + } + const pluginPath = resolve.sync(plugin, { basedir: process.cwd() }); return eval("require")(pluginPath); }); diff --git a/src/language-graphql/printer-graphql.js b/src/language-graphql/printer-graphql.js index c86157440..53b3cf778 100644 --- a/src/language-graphql/printer-graphql.js +++ b/src/language-graphql/printer-graphql.js @@ -539,7 +539,24 @@ function printSequence(sequencePath, options, print) { }); } +function canAttachComment(node) { + return node.kind && node.kind !== "Comment"; +} + +function printComment(commentPath) { + const comment = commentPath.getValue(); + + switch (comment.kind) { + case "Comment": + return "#" + comment.value.trimRight(); + default: + throw new Error("Not a comment: " + JSON.stringify(comment)); + } +} + module.exports = { print: genericPrint, - hasPrettierIgnore: util.hasIgnoreComment + hasPrettierIgnore: util.hasIgnoreComment, + printComment, + canAttachComment }; diff --git a/src/language-js/printer-estree.js b/src/language-js/printer-estree.js index 27734bd8c..5e84516d7 100644 --- a/src/language-js/printer-estree.js +++ b/src/language-js/printer-estree.js @@ -5167,9 +5167,77 @@ function willPrintOwnComments(path) { ); } +function canAttachComment(node) { + return ( + node.type && + node.type !== "CommentBlock" && + node.type !== "CommentLine" && + node.type !== "Line" && + node.type !== "Block" && + node.type !== "EmptyStatement" && + node.type !== "TemplateElement" && + node.type !== "Import" && + !(node.callee && node.callee.type === "Import") + ); +} + +function printComment(commentPath, options) { + const comment = commentPath.getValue(); + + switch (comment.type) { + case "CommentBlock": + case "Block": { + if (isJsDocComment(comment)) { + return printJsDocComment(comment); + } + + const isInsideFlowComment = + options.originalText.substr(util.locEnd(comment) - 3, 3) === "*-/"; + + return "/*" + comment.value + (isInsideFlowComment ? "*-/" : "*/"); + } + case "CommentLine": + case "Line": + // Print shebangs with the proper comment characters + if (options.originalText.slice(util.locStart(comment)).startsWith("#!")) { + return "#!" + comment.value.trimRight(); + } + return "//" + comment.value.trimRight(); + default: + throw new Error("Not a comment: " + JSON.stringify(comment)); + } +} + +function isJsDocComment(comment) { + const lines = comment.value.split("\n"); + return ( + lines.length > 1 && + lines.slice(0, lines.length - 1).every(line => line.trim()[0] === "*") + ); +} + +function printJsDocComment(comment) { + const lines = comment.value.split("\n"); + + return concat([ + "/*", + join( + hardline, + lines.map( + (line, index) => + (index > 0 ? " " : "") + + (index < lines.length - 1 ? line.trim() : line.trimLeft()) + ) + ), + "*/" + ]); +} + module.exports = { print: genericPrint, embed, hasPrettierIgnore, - willPrintOwnComments + willPrintOwnComments, + canAttachComment, + printComment }; diff --git a/src/main/ast-to-doc.js b/src/main/ast-to-doc.js index d4f333cf5..7ebaa5401 100644 --- a/src/main/ast-to-doc.js +++ b/src/main/ast-to-doc.js @@ -12,12 +12,11 @@ const concat = docBuilders.concat; const hardline = docBuilders.hardline; const addAlignmentToDoc = docBuilders.addAlignmentToDoc; const docUtils = doc.utils; -const getPrinter = require("./get-printer"); function printAstToDoc(ast, options, addAlignmentSize) { addAlignmentSize = addAlignmentSize || 0; - const printer = getPrinter(options); + const printer = options.printer; const cache = new Map(); function printGenerically(path, args) { @@ -32,11 +31,11 @@ function printAstToDoc(ast, options, addAlignmentSize) { // UnionTypeAnnotation has to align the child without the comments let res; if (printer.willPrintOwnComments && printer.willPrintOwnComments(path)) { - res = genericPrint(path, options, printer, printGenerically, args); + res = genericPrint(path, options, printGenerically, args); } else { res = comments.printComments( path, - p => genericPrint(p, options, printer, printGenerically, args), + p => genericPrint(p, options, printGenerically, args), options, args && args.needsSemi ); @@ -68,10 +67,11 @@ function printAstToDoc(ast, options, addAlignmentSize) { return doc; } -function genericPrint(path, options, printer, printPath, args) { +function genericPrint(path, options, printPath, args) { assert.ok(path instanceof FastPath); const node = path.getValue(); + const printer = options.printer; // Escape hatch if (printer.hasPrettierIgnore && printer.hasPrettierIgnore(path)) { @@ -81,7 +81,7 @@ function genericPrint(path, options, printer, printPath, args) { if (node) { try { // Potentially switch to a different parser - const sub = multiparser.printSubtree(printer, path, printPath, options); + const sub = multiparser.printSubtree(path, printPath, options); if (sub) { return sub; } diff --git a/src/main/comments.js b/src/main/comments.js index 5c2e1d31c..ad2fe68d6 100644 --- a/src/main/comments.js +++ b/src/main/comments.js @@ -18,25 +18,14 @@ const getNextNonSpaceNonCommentCharacter = const getNextNonSpaceNonCommentCharacterIndex = util.getNextNonSpaceNonCommentCharacterIndex; -function getSortedChildNodes(node, text, resultArray) { +function getSortedChildNodes(node, text, options, resultArray) { if (!node) { return; } + const printer = options.printer; if (resultArray) { - if ( - node && - ((node.type && - node.type !== "CommentBlock" && - node.type !== "CommentLine" && - node.type !== "Line" && - node.type !== "Block" && - node.type !== "EmptyStatement" && - node.type !== "TemplateElement" && - node.type !== "Import" && - !(node.callee && node.callee.type === "Import")) || - (node.kind && node.kind !== "Comment")) - ) { + if (node && printer.canAttachComment && printer.canAttachComment(node)) { // This reverse insertion sort almost always takes constant // time because we almost always (maybe always?) append the // nodes in order anyway. @@ -74,7 +63,7 @@ function getSortedChildNodes(node, text, resultArray) { } for (let i = 0, nameCount = names.length; i < nameCount; ++i) { - getSortedChildNodes(node[names[i]], text, resultArray); + getSortedChildNodes(node[names[i]], text, options, resultArray); } return resultArray; @@ -83,8 +72,8 @@ function getSortedChildNodes(node, text, resultArray) { // As efficiently as possible, decorate the comment object with // .precedingNode, .enclosingNode, and/or .followingNode properties, at // least one of which is guaranteed to be defined. -function decorateComment(node, comment, text) { - const childNodes = getSortedChildNodes(node, text); +function decorateComment(node, comment, text, options) { + const childNodes = getSortedChildNodes(node, text, options); let precedingNode; let followingNode; // Time to dust off the old binary search robes and wizard hat. @@ -101,7 +90,7 @@ function decorateComment(node, comment, text) { // The comment is completely contained by this child node. comment.enclosingNode = child; - decorateComment(child, comment, text); + decorateComment(child, comment, text, options); return; // Abandon the binary search at this level. } @@ -174,7 +163,7 @@ function attach(comments, ast, text, options) { return; } - decorateComment(ast, comment, text); + decorateComment(ast, comment, text, options); const precedingNode = comment.precedingNode; const enclosingNode = comment.enclosingNode; @@ -923,56 +912,7 @@ function handleVariableDeclaratorComments( function printComment(commentPath, options) { const comment = commentPath.getValue(); comment.printed = true; - - switch (comment.type || comment.kind) { - case "Comment": - return "#" + comment.value.trimRight(); - case "CommentBlock": - case "Block": { - if (isJsDocComment(comment)) { - return printJsDocComment(comment); - } - - const isInsideFlowComment = - options.originalText.substr(util.locEnd(comment) - 3, 3) === "*-/"; - - return "/*" + comment.value + (isInsideFlowComment ? "*-/" : "*/"); - } - case "CommentLine": - case "Line": - // Print shebangs with the proper comment characters - if (options.originalText.slice(util.locStart(comment)).startsWith("#!")) { - return "#!" + comment.value.trimRight(); - } - return "//" + comment.value.trimRight(); - default: - throw new Error("Not a comment: " + JSON.stringify(comment)); - } -} - -function isJsDocComment(comment) { - const lines = comment.value.split("\n"); - return ( - lines.length > 1 && - lines.slice(0, lines.length - 1).every(line => line.trim()[0] === "*") - ); -} - -function printJsDocComment(comment) { - const lines = comment.value.split("\n"); - - return concat([ - "/*", - join( - hardline, - lines.map( - (line, index) => - (index > 0 ? " " : "") + - (index < lines.length - 1 ? line.trim() : line.trimLeft()) - ) - ), - "*/" - ]); + return options.printer.printComment(commentPath, options); } function findExpressionIndexForComment(quasis, comment) { diff --git a/src/common/deprecated.js b/src/main/deprecated.js similarity index 100% rename from src/common/deprecated.js rename to src/main/deprecated.js diff --git a/src/main/get-printer.js b/src/main/get-printer.js index 86c7eb925..cba1b1005 100644 --- a/src/main/get-printer.js +++ b/src/main/get-printer.js @@ -1,13 +1,14 @@ "use strict"; -const loadPlugins = require("../common/load-plugins"); -const parser = require("./parser"); - function getPrinter(options) { - const plugins = loadPlugins(options); - const parsers = parser.getParsers(plugins, options); - const astFormat = parser.resolveParser(parsers, options).astFormat; - const printerPlugin = plugins.find(plugin => plugin.printers[astFormat]); + const astFormat = options.astFormat; + + if (!astFormat) { + throw new Error("getPrinter() requires astFormat to be set"); + } + const printerPlugin = options.plugins.find( + plugin => plugin.printers[astFormat] + ); if (!printerPlugin) { throw new Error( `Couldn't find printer plugin for AST format "${astFormat}"` diff --git a/src/main/multiparser.js b/src/main/multiparser.js index 8c635782a..3704e658a 100644 --- a/src/main/multiparser.js +++ b/src/main/multiparser.js @@ -1,10 +1,11 @@ "use strict"; +const normalize = require("./options").normalize; const comments = require("./comments"); -function printSubtree(printer, path, print, options) { - if (printer.embed) { - return printer.embed( +function printSubtree(path, print, options) { + if (options.printer.embed) { + return options.printer.embed( path, print, (text, partialNextOptions) => @@ -15,13 +16,12 @@ function printSubtree(printer, path, print, options) { } function textToDoc(text, partialNextOptions, parentOptions) { - const nextOptions = Object.assign({}, parentOptions, partialNextOptions, { - parentParser: parentOptions.parser, - originalText: text - }); - if (nextOptions.parser === "json") { - nextOptions.trailingComma = "none"; - } + const nextOptions = normalize( + Object.assign({}, parentOptions, partialNextOptions, { + parentParser: parentOptions.parser, + originalText: text + }) + ); const ast = require("./parser").parse(text, nextOptions); const astComments = ast.comments; diff --git a/src/common/options.js b/src/main/options.js similarity index 86% rename from src/common/options.js rename to src/main/options.js index b47d8140f..73932f27f 100644 --- a/src/common/options.js +++ b/src/main/options.js @@ -4,7 +4,10 @@ const path = require("path"); const validate = require("jest-validate").validate; const deprecatedConfig = require("./deprecated"); -const getSupportInfo = require("./support").getSupportInfo; +const getSupportInfo = require("../common/support").getSupportInfo; +const loadPlugins = require("../common/load-plugins"); +const resolveParser = require("./parser").resolveParser; +const getPrinter = require("./get-printer"); const defaults = { cursorOffset: -1, @@ -18,12 +21,16 @@ const defaults = { bracketSpacing: true, jsxBracketSameLine: false, parser: "babylon", + parentParser: "", insertPragma: false, requirePragma: false, semi: true, proseWrap: "preserve", arrowParens: "avoid", - plugins: [] + plugins: [], + astFormat: "estree", + printer: {}, + __inJsTemplate: false }; const exampleConfig = Object.assign({}, defaults, { @@ -39,6 +46,7 @@ function normalize(options) { if ( filepath && + !normalized.parentParser && (!normalized.parser || normalized.parser === defaults.parser) ) { const extension = path.extname(filepath); @@ -114,6 +122,10 @@ function normalize(options) { delete normalized.useFlowParser; } + normalized.plugins = loadPlugins(normalized); + normalized.astFormat = resolveParser(normalized).astFormat; + normalized.printer = getPrinter(normalized); + Object.keys(defaults).forEach(k => { if (normalized[k] == null) { normalized[k] = defaults[k]; diff --git a/src/main/parser.js b/src/main/parser.js index 20b25467a..6e04267ad 100644 --- a/src/main/parser.js +++ b/src/main/parser.js @@ -2,16 +2,17 @@ const path = require("path"); const ConfigError = require("../common/errors").ConfigError; -const loadPlugins = require("../common/load-plugins"); -function getParsers(plugins) { - return plugins.reduce( +function getParsers(options) { + return options.plugins.reduce( (parsers, plugin) => Object.assign({}, parsers, plugin.parsers), {} ); } -function resolveParser(parsers, opts) { +function resolveParser(opts, parsers) { + parsers = parsers || getParsers(opts); + if (typeof opts.parser === "function") { // Custom parser API always works with JavaScript. return { @@ -39,7 +40,7 @@ function resolveParser(parsers, opts) { } function parse(text, opts) { - const parsers = getParsers(loadPlugins(opts), opts); + const parsers = getParsers(opts); // Copy the "parse" function from parser to a new object whose values are // functions. Use defineProperty()/getOwnPropertyDescriptor() such that we @@ -54,7 +55,7 @@ function parse(text, opts) { {} ); - const parser = resolveParser(parsers, opts); + const parser = resolveParser(opts, parsers); try { return parser.parse(text, parsersForCustomParserApi, opts); @@ -75,4 +76,4 @@ function parse(text, opts) { } } -module.exports = { getParsers, parse, resolveParser }; +module.exports = { parse, resolveParser }; diff --git a/tests_config/run_spec.js b/tests_config/run_spec.js index bfc649e58..9baaaff31 100644 --- a/tests_config/run_spec.js +++ b/tests_config/run_spec.js @@ -3,7 +3,6 @@ const fs = require("fs"); const extname = require("path").extname; const prettier = require("./require_prettier"); -const parser = require("../src/main/parser"); const massageAST = require("../src/common/clean-ast.js").massageAST; const AST_COMPARE = process.env["AST_COMPARE"]; @@ -116,7 +115,7 @@ function stripLocation(ast) { } function parse(string, opts) { - return stripLocation(parser.parse(string, opts)); + return stripLocation(prettier.__debug.parse(string, opts)); } function prettyprint(src, filename, options) {