Separate entry for web bundle (#4386)
parent
9cc5d4f162
commit
8015e417c0
450
index.js
450
index.js
|
@ -2,413 +2,39 @@
|
|||
|
||||
const version = require("./package.json").version;
|
||||
|
||||
const privateUtil = require("./src/common/util");
|
||||
const sharedUtil = require("./src/common/util-shared");
|
||||
const core = require("./src/main/core");
|
||||
const getSupportInfo = require("./src/main/support").getSupportInfo;
|
||||
const sharedUtil = require("./src/common/util-shared");
|
||||
const loadPlugins = require("./src/common/load-plugins");
|
||||
const massageAST = require("./src/main/massage-ast");
|
||||
|
||||
const comments = require("./src/main/comments");
|
||||
const printAstToDoc = require("./src/main/ast-to-doc");
|
||||
const rawNormalizeOptions = 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;
|
||||
|
||||
function withPlugins(opts) {
|
||||
return Object.assign({}, opts, {
|
||||
plugins: loadPlugins(opts && opts.plugins)
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeOptions(opts) {
|
||||
return rawNormalizeOptions(withPlugins(opts));
|
||||
}
|
||||
|
||||
function guessLineEnding(text) {
|
||||
const index = text.indexOf("\n");
|
||||
if (index >= 0 && text.charAt(index - 1) === "\r") {
|
||||
return "\r\n";
|
||||
}
|
||||
return "\n";
|
||||
}
|
||||
|
||||
function attachComments(text, ast, opts) {
|
||||
const astComments = ast.comments;
|
||||
if (astComments) {
|
||||
delete ast.comments;
|
||||
comments.attach(astComments, ast, text, opts);
|
||||
}
|
||||
ast.tokens = [];
|
||||
opts.originalText = text.trimRight();
|
||||
return astComments;
|
||||
}
|
||||
|
||||
function ensureAllCommentsPrinted(astComments) {
|
||||
if (!astComments) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < astComments.length; ++i) {
|
||||
if (astComments[i].value.trim() === "prettier-ignore") {
|
||||
// If there's a prettier-ignore, we're not printing that sub-tree so we
|
||||
// don't know if the comments was printed or not.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
astComments.forEach(comment => {
|
||||
if (!comment.printed) {
|
||||
throw new Error(
|
||||
'Comment "' +
|
||||
comment.value.trim() +
|
||||
'" was not printed. Please report this error!'
|
||||
);
|
||||
}
|
||||
delete comment.printed;
|
||||
});
|
||||
}
|
||||
|
||||
function formatWithCursor(text, opts, addAlignmentSize) {
|
||||
const selectedParser = parser.resolveParser(opts);
|
||||
const hasPragma = !selectedParser.hasPragma || selectedParser.hasPragma(text);
|
||||
if (opts.requirePragma && !hasPragma) {
|
||||
return { formatted: text };
|
||||
}
|
||||
|
||||
const UTF8BOM = 0xfeff;
|
||||
const hasUnicodeBOM = text.charCodeAt(0) === UTF8BOM;
|
||||
if (hasUnicodeBOM) {
|
||||
text = text.substring(1);
|
||||
}
|
||||
|
||||
if (
|
||||
opts.insertPragma &&
|
||||
opts.printer.insertPragma &&
|
||||
!hasPragma &&
|
||||
opts.rangeStart === 0 &&
|
||||
opts.rangeEnd === Infinity
|
||||
) {
|
||||
text = opts.printer.insertPragma(text);
|
||||
}
|
||||
|
||||
addAlignmentSize = addAlignmentSize || 0;
|
||||
|
||||
const result = parser.parse(text, opts);
|
||||
const ast = result.ast;
|
||||
text = result.text;
|
||||
|
||||
const formattedRangeOnly = formatRange(text, opts, ast);
|
||||
if (formattedRangeOnly) {
|
||||
return { formatted: formattedRangeOnly };
|
||||
}
|
||||
|
||||
let cursorOffset;
|
||||
if (opts.cursorOffset >= 0) {
|
||||
const cursorNodeAndParents = findNodeAtOffset(ast, opts.cursorOffset, opts);
|
||||
const cursorNode = cursorNodeAndParents.node;
|
||||
if (cursorNode) {
|
||||
cursorOffset = opts.cursorOffset - opts.locStart(cursorNode);
|
||||
opts.cursorNode = cursorNode;
|
||||
}
|
||||
}
|
||||
|
||||
const astComments = attachComments(text, ast, opts);
|
||||
const doc = printAstToDoc(ast, opts, addAlignmentSize);
|
||||
opts.newLine = guessLineEnding(text);
|
||||
const toStringResult = printDocToString(doc, opts);
|
||||
let str = toStringResult.formatted;
|
||||
if (hasUnicodeBOM) {
|
||||
str = String.fromCharCode(UTF8BOM) + str;
|
||||
}
|
||||
const cursorOffsetResult = toStringResult.cursor;
|
||||
ensureAllCommentsPrinted(astComments);
|
||||
// Remove extra leading indentation as well as the added indentation after last newline
|
||||
if (addAlignmentSize > 0) {
|
||||
return { formatted: str.trim() + opts.newLine };
|
||||
}
|
||||
|
||||
if (cursorOffset !== undefined) {
|
||||
return {
|
||||
formatted: str,
|
||||
cursorOffset: cursorOffsetResult + cursorOffset
|
||||
};
|
||||
}
|
||||
|
||||
return { formatted: str };
|
||||
}
|
||||
|
||||
function format(text, opts, addAlignmentSize) {
|
||||
return formatWithCursor(text, opts, addAlignmentSize).formatted;
|
||||
}
|
||||
|
||||
function findSiblingAncestors(startNodeAndParents, endNodeAndParents, opts) {
|
||||
let resultStartNode = startNodeAndParents.node;
|
||||
let resultEndNode = endNodeAndParents.node;
|
||||
|
||||
if (resultStartNode === resultEndNode) {
|
||||
return {
|
||||
startNode: resultStartNode,
|
||||
endNode: resultEndNode
|
||||
};
|
||||
}
|
||||
|
||||
for (const endParent of endNodeAndParents.parentNodes) {
|
||||
if (
|
||||
endParent.type !== "Program" &&
|
||||
endParent.type !== "File" &&
|
||||
opts.locStart(endParent) >= opts.locStart(startNodeAndParents.node)
|
||||
) {
|
||||
resultEndNode = endParent;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (const startParent of startNodeAndParents.parentNodes) {
|
||||
if (
|
||||
startParent.type !== "Program" &&
|
||||
startParent.type !== "File" &&
|
||||
opts.locEnd(startParent) <= opts.locEnd(endNodeAndParents.node)
|
||||
) {
|
||||
resultStartNode = startParent;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
startNode: resultStartNode,
|
||||
endNode: resultEndNode
|
||||
// Luckily `opts` is always the 2nd argument
|
||||
function withPlugins(fn) {
|
||||
return function() {
|
||||
const args = Array.from(arguments);
|
||||
const opts = args[1] || {};
|
||||
args[1] = Object.assign({}, opts, {
|
||||
plugins: loadPlugins(opts.plugins)
|
||||
});
|
||||
return fn.apply(null, args);
|
||||
};
|
||||
}
|
||||
|
||||
function findNodeAtOffset(node, offset, options, predicate, parentNodes) {
|
||||
predicate = predicate || (() => true);
|
||||
parentNodes = parentNodes || [];
|
||||
const start = options.locStart(node, options.locStart);
|
||||
const end = options.locEnd(node, options.locEnd);
|
||||
if (start <= offset && offset <= end) {
|
||||
for (const childNode of comments.getSortedChildNodes(
|
||||
node,
|
||||
undefined /* text */,
|
||||
options
|
||||
)) {
|
||||
const childResult = findNodeAtOffset(
|
||||
childNode,
|
||||
offset,
|
||||
options,
|
||||
predicate,
|
||||
[node].concat(parentNodes)
|
||||
);
|
||||
if (childResult) {
|
||||
return childResult;
|
||||
}
|
||||
}
|
||||
|
||||
if (predicate(node)) {
|
||||
return {
|
||||
node: node,
|
||||
parentNodes: parentNodes
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// See https://www.ecma-international.org/ecma-262/5.1/#sec-A.5
|
||||
function isSourceElement(opts, node) {
|
||||
if (node == null) {
|
||||
return false;
|
||||
}
|
||||
// JS and JS like to avoid repetitions
|
||||
const jsSourceElements = [
|
||||
"FunctionDeclaration",
|
||||
"BlockStatement",
|
||||
"BreakStatement",
|
||||
"ContinueStatement",
|
||||
"DebuggerStatement",
|
||||
"DoWhileStatement",
|
||||
"EmptyStatement",
|
||||
"ExpressionStatement",
|
||||
"ForInStatement",
|
||||
"ForStatement",
|
||||
"IfStatement",
|
||||
"LabeledStatement",
|
||||
"ReturnStatement",
|
||||
"SwitchStatement",
|
||||
"ThrowStatement",
|
||||
"TryStatement",
|
||||
"VariableDeclaration",
|
||||
"WhileStatement",
|
||||
"WithStatement",
|
||||
"ClassDeclaration", // ES 2015
|
||||
"ImportDeclaration", // Module
|
||||
"ExportDefaultDeclaration", // Module
|
||||
"ExportNamedDeclaration", // Module
|
||||
"ExportAllDeclaration", // Module
|
||||
"TypeAlias", // Flow
|
||||
"InterfaceDeclaration", // Flow, TypeScript
|
||||
"TypeAliasDeclaration", // TypeScript
|
||||
"ExportAssignment", // TypeScript
|
||||
"ExportDeclaration" // TypeScript
|
||||
];
|
||||
const jsonSourceElements = [
|
||||
"ObjectExpression",
|
||||
"ArrayExpression",
|
||||
"StringLiteral",
|
||||
"NumericLiteral",
|
||||
"BooleanLiteral",
|
||||
"NullLiteral"
|
||||
];
|
||||
const graphqlSourceElements = [
|
||||
"OperationDefinition",
|
||||
"FragmentDefinition",
|
||||
"VariableDefinition",
|
||||
"TypeExtensionDefinition",
|
||||
"ObjectTypeDefinition",
|
||||
"FieldDefinition",
|
||||
"DirectiveDefinition",
|
||||
"EnumTypeDefinition",
|
||||
"EnumValueDefinition",
|
||||
"InputValueDefinition",
|
||||
"InputObjectTypeDefinition",
|
||||
"SchemaDefinition",
|
||||
"OperationTypeDefinition",
|
||||
"InterfaceTypeDefinition",
|
||||
"UnionTypeDefinition",
|
||||
"ScalarTypeDefinition"
|
||||
];
|
||||
switch (opts.parser) {
|
||||
case "flow":
|
||||
case "babylon":
|
||||
case "typescript":
|
||||
return jsSourceElements.indexOf(node.type) > -1;
|
||||
case "json":
|
||||
return jsonSourceElements.indexOf(node.type) > -1;
|
||||
case "graphql":
|
||||
return graphqlSourceElements.indexOf(node.kind) > -1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function calculateRange(text, opts, ast) {
|
||||
// Contract the range so that it has non-whitespace characters at its endpoints.
|
||||
// This ensures we can format a range that doesn't end on a node.
|
||||
const rangeStringOrig = text.slice(opts.rangeStart, opts.rangeEnd);
|
||||
const startNonWhitespace = Math.max(
|
||||
opts.rangeStart + rangeStringOrig.search(/\S/),
|
||||
opts.rangeStart
|
||||
);
|
||||
let endNonWhitespace;
|
||||
for (
|
||||
endNonWhitespace = opts.rangeEnd;
|
||||
endNonWhitespace > opts.rangeStart;
|
||||
--endNonWhitespace
|
||||
) {
|
||||
if (text[endNonWhitespace - 1].match(/\S/)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const startNodeAndParents = findNodeAtOffset(
|
||||
ast,
|
||||
startNonWhitespace,
|
||||
opts,
|
||||
node => isSourceElement(opts, node)
|
||||
);
|
||||
const endNodeAndParents = findNodeAtOffset(
|
||||
ast,
|
||||
endNonWhitespace,
|
||||
opts,
|
||||
node => isSourceElement(opts, node)
|
||||
);
|
||||
|
||||
if (!startNodeAndParents || !endNodeAndParents) {
|
||||
return {
|
||||
rangeStart: 0,
|
||||
rangeEnd: 0
|
||||
};
|
||||
}
|
||||
|
||||
const siblingAncestors = findSiblingAncestors(
|
||||
startNodeAndParents,
|
||||
endNodeAndParents,
|
||||
opts
|
||||
);
|
||||
const startNode = siblingAncestors.startNode;
|
||||
const endNode = siblingAncestors.endNode;
|
||||
const rangeStart = Math.min(
|
||||
opts.locStart(startNode, opts.locStart),
|
||||
opts.locStart(endNode, opts.locStart)
|
||||
);
|
||||
const rangeEnd = Math.max(
|
||||
opts.locEnd(startNode, opts.locEnd),
|
||||
opts.locEnd(endNode, opts.locEnd)
|
||||
);
|
||||
|
||||
return {
|
||||
rangeStart: rangeStart,
|
||||
rangeEnd: rangeEnd
|
||||
};
|
||||
}
|
||||
|
||||
function formatRange(text, opts, ast) {
|
||||
if (opts.rangeStart <= 0 && text.length <= opts.rangeEnd) {
|
||||
return;
|
||||
}
|
||||
|
||||
const range = calculateRange(text, opts, ast);
|
||||
const rangeStart = range.rangeStart;
|
||||
const rangeEnd = range.rangeEnd;
|
||||
const rangeString = text.slice(rangeStart, rangeEnd);
|
||||
|
||||
// Try to extend the range backwards to the beginning of the line.
|
||||
// This is so we can detect indentation correctly and restore it.
|
||||
// Use `Math.min` since `lastIndexOf` returns 0 when `rangeStart` is 0
|
||||
const rangeStart2 = Math.min(
|
||||
rangeStart,
|
||||
text.lastIndexOf("\n", rangeStart) + 1
|
||||
);
|
||||
const indentString = text.slice(rangeStart2, rangeStart);
|
||||
|
||||
const alignmentSize = privateUtil.getAlignmentSize(
|
||||
indentString,
|
||||
opts.tabWidth
|
||||
);
|
||||
|
||||
const rangeFormatted = format(
|
||||
rangeString,
|
||||
Object.assign({}, opts, {
|
||||
rangeStart: 0,
|
||||
rangeEnd: Infinity,
|
||||
printWidth: opts.printWidth - alignmentSize
|
||||
}),
|
||||
alignmentSize
|
||||
);
|
||||
|
||||
// Since the range contracts to avoid trailing whitespace,
|
||||
// we need to remove the newline that was inserted by the `format` call.
|
||||
const rangeTrimmed = rangeFormatted.trimRight();
|
||||
|
||||
return text.slice(0, rangeStart) + rangeTrimmed + text.slice(rangeEnd);
|
||||
}
|
||||
const formatWithCursor = withPlugins(core.formatWithCursor);
|
||||
|
||||
module.exports = {
|
||||
formatWithCursor: function(text, opts) {
|
||||
return formatWithCursor(text, normalizeOptions(opts));
|
||||
},
|
||||
formatWithCursor,
|
||||
|
||||
format: function(text, opts) {
|
||||
return format(text, normalizeOptions(opts));
|
||||
format(text, opts) {
|
||||
return formatWithCursor(text, opts).formatted;
|
||||
},
|
||||
|
||||
check: function(text, opts) {
|
||||
try {
|
||||
const formatted = format(text, normalizeOptions(opts));
|
||||
const formatted = formatWithCursor(text, opts).formatted;
|
||||
return formatted === text;
|
||||
} catch (e) {
|
||||
return false;
|
||||
|
@ -421,9 +47,7 @@ module.exports = {
|
|||
resolveConfigFile: config.resolveConfigFile,
|
||||
clearConfigCache: config.clearCache,
|
||||
|
||||
getSupportInfo(version, opts) {
|
||||
return getSupportInfo(version, withPlugins(opts));
|
||||
},
|
||||
getSupportInfo: withPlugins(getSupportInfo),
|
||||
|
||||
version,
|
||||
|
||||
|
@ -431,40 +55,10 @@ module.exports = {
|
|||
|
||||
/* istanbul ignore next */
|
||||
__debug: {
|
||||
parse: function(text, opts, massage) {
|
||||
opts = normalizeOptions(opts);
|
||||
const parsed = parser.parse(text, opts);
|
||||
if (massage) {
|
||||
parsed.ast = massageAST(parsed.ast, opts);
|
||||
}
|
||||
return parsed;
|
||||
},
|
||||
formatAST: function(ast, opts) {
|
||||
opts = normalizeOptions(opts);
|
||||
const doc = printAstToDoc(ast, opts);
|
||||
const str = printDocToString(doc, opts);
|
||||
return str;
|
||||
},
|
||||
// Doesn't handle shebang for now
|
||||
formatDoc: function(doc, opts) {
|
||||
opts = normalizeOptions(opts);
|
||||
const debug = printDocToDebug(doc);
|
||||
const str = format(debug, opts);
|
||||
return str;
|
||||
},
|
||||
printToDoc: function(text, opts) {
|
||||
opts = normalizeOptions(opts);
|
||||
const result = parser.parse(text, opts);
|
||||
const ast = result.ast;
|
||||
text = result.text;
|
||||
attachComments(text, ast, opts);
|
||||
const doc = printAstToDoc(ast, opts);
|
||||
return doc;
|
||||
},
|
||||
printDocToString: function(doc, opts) {
|
||||
opts = normalizeOptions(opts);
|
||||
const str = printDocToString(doc, opts);
|
||||
return str;
|
||||
}
|
||||
parse: withPlugins(core.parse),
|
||||
formatAST: withPlugins(core.formatAST),
|
||||
formatDoc: withPlugins(core.formatDoc),
|
||||
printToDoc: withPlugins(core.printToDoc),
|
||||
printDocToString: withPlugins(core.printDocToString)
|
||||
}
|
||||
};
|
||||
|
|
|
@ -10,6 +10,7 @@ module.exports = {
|
|||
collectCoverage: ENABLE_COVERAGE,
|
||||
collectCoverageFrom: ["src/**/*.js", "index.js", "!<rootDir>/node_modules/"],
|
||||
coveragePathIgnorePatterns: [
|
||||
"<rootDir>/web.js",
|
||||
"<rootDir>/src/doc/doc-debug.js",
|
||||
"<rootDir>/src/main/massage-ast.js"
|
||||
],
|
||||
|
|
|
@ -0,0 +1,213 @@
|
|||
"use strict";
|
||||
|
||||
const normalizeOptions = require("./options").normalize;
|
||||
const massageAST = require("./massage-ast");
|
||||
const comments = require("./comments");
|
||||
const parser = require("./parser");
|
||||
const printAstToDoc = require("./ast-to-doc");
|
||||
const rangeUtil = require("./range-util");
|
||||
|
||||
const privateUtil = require("../common/util");
|
||||
|
||||
const doc = require("../doc");
|
||||
const printDocToString = doc.printer.printDocToString;
|
||||
const printDocToDebug = doc.debug.printDocToDebug;
|
||||
|
||||
const UTF8BOM = 0xfeff;
|
||||
|
||||
function guessLineEnding(text) {
|
||||
const index = text.indexOf("\n");
|
||||
if (index >= 0 && text.charAt(index - 1) === "\r") {
|
||||
return "\r\n";
|
||||
}
|
||||
return "\n";
|
||||
}
|
||||
|
||||
function ensureAllCommentsPrinted(astComments) {
|
||||
if (!astComments) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < astComments.length; ++i) {
|
||||
if (astComments[i].value.trim() === "prettier-ignore") {
|
||||
// If there's a prettier-ignore, we're not printing that sub-tree so we
|
||||
// don't know if the comments was printed or not.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
astComments.forEach(comment => {
|
||||
if (!comment.printed) {
|
||||
throw new Error(
|
||||
'Comment "' +
|
||||
comment.value.trim() +
|
||||
'" was not printed. Please report this error!'
|
||||
);
|
||||
}
|
||||
delete comment.printed;
|
||||
});
|
||||
}
|
||||
|
||||
function attachComments(text, ast, opts) {
|
||||
const astComments = ast.comments;
|
||||
if (astComments) {
|
||||
delete ast.comments;
|
||||
comments.attach(astComments, ast, text, opts);
|
||||
}
|
||||
ast.tokens = [];
|
||||
opts.originalText = text.trimRight();
|
||||
return astComments;
|
||||
}
|
||||
|
||||
function coreFormat(text, opts, addAlignmentSize) {
|
||||
addAlignmentSize = addAlignmentSize || 0;
|
||||
|
||||
const parsed = parser.parse(text, opts);
|
||||
const ast = parsed.ast;
|
||||
text = parsed.text;
|
||||
|
||||
let cursorOffset;
|
||||
if (opts.cursorOffset >= 0) {
|
||||
const cursorNodeAndParents = rangeUtil.findNodeAtOffset(
|
||||
ast,
|
||||
opts.cursorOffset,
|
||||
opts
|
||||
);
|
||||
const cursorNode = cursorNodeAndParents.node;
|
||||
if (cursorNode) {
|
||||
cursorOffset = opts.cursorOffset - opts.locStart(cursorNode);
|
||||
opts.cursorNode = cursorNode;
|
||||
}
|
||||
}
|
||||
|
||||
const astComments = attachComments(text, ast, opts);
|
||||
const doc = printAstToDoc(ast, opts, addAlignmentSize);
|
||||
opts.newLine = guessLineEnding(text);
|
||||
|
||||
const result = printDocToString(doc, opts);
|
||||
const formatted = result.formatted;
|
||||
const cursorOffsetResult = result.cursor;
|
||||
ensureAllCommentsPrinted(astComments);
|
||||
// Remove extra leading indentation as well as the added indentation after last newline
|
||||
if (addAlignmentSize > 0) {
|
||||
return { formatted: formatted.trim() + opts.newLine };
|
||||
}
|
||||
|
||||
if (cursorOffset !== undefined) {
|
||||
return {
|
||||
formatted,
|
||||
cursorOffset: cursorOffsetResult + cursorOffset
|
||||
};
|
||||
}
|
||||
|
||||
return { formatted };
|
||||
}
|
||||
|
||||
function formatRange(text, opts) {
|
||||
const parsed = parser.parse(text, opts);
|
||||
const ast = parsed.ast;
|
||||
text = parsed.text;
|
||||
|
||||
const range = rangeUtil.calculateRange(text, opts, ast);
|
||||
const rangeStart = range.rangeStart;
|
||||
const rangeEnd = range.rangeEnd;
|
||||
const rangeString = text.slice(rangeStart, rangeEnd);
|
||||
|
||||
// Try to extend the range backwards to the beginning of the line.
|
||||
// This is so we can detect indentation correctly and restore it.
|
||||
// Use `Math.min` since `lastIndexOf` returns 0 when `rangeStart` is 0
|
||||
const rangeStart2 = Math.min(
|
||||
rangeStart,
|
||||
text.lastIndexOf("\n", rangeStart) + 1
|
||||
);
|
||||
const indentString = text.slice(rangeStart2, rangeStart);
|
||||
|
||||
const alignmentSize = privateUtil.getAlignmentSize(
|
||||
indentString,
|
||||
opts.tabWidth
|
||||
);
|
||||
|
||||
const rangeFormatted = coreFormat(
|
||||
rangeString,
|
||||
Object.assign({}, opts, {
|
||||
rangeStart: 0,
|
||||
rangeEnd: Infinity,
|
||||
printWidth: opts.printWidth - alignmentSize
|
||||
}),
|
||||
alignmentSize
|
||||
).formatted;
|
||||
|
||||
// Since the range contracts to avoid trailing whitespace,
|
||||
// we need to remove the newline that was inserted by the `format` call.
|
||||
const rangeTrimmed = rangeFormatted.trimRight();
|
||||
|
||||
return text.slice(0, rangeStart) + rangeTrimmed + text.slice(rangeEnd);
|
||||
}
|
||||
|
||||
function format(text, opts) {
|
||||
if (opts.rangeStart > 0 || opts.rangeEnd < text.length) {
|
||||
return { formatted: formatRange(text, opts) };
|
||||
}
|
||||
|
||||
const selectedParser = parser.resolveParser(opts);
|
||||
const hasPragma = !selectedParser.hasPragma || selectedParser.hasPragma(text);
|
||||
if (opts.requirePragma && !hasPragma) {
|
||||
return { formatted: text };
|
||||
}
|
||||
|
||||
const hasUnicodeBOM = text.charCodeAt(0) === UTF8BOM;
|
||||
if (hasUnicodeBOM) {
|
||||
text = text.substring(1);
|
||||
}
|
||||
|
||||
if (opts.insertPragma && opts.printer.insertPragma && !hasPragma) {
|
||||
text = opts.printer.insertPragma(text);
|
||||
}
|
||||
|
||||
const result = coreFormat(text, opts);
|
||||
if (hasUnicodeBOM) {
|
||||
result.formatted = String.fromCharCode(UTF8BOM) + result.formatted;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
formatWithCursor(text, opts) {
|
||||
return format(text, normalizeOptions(opts));
|
||||
},
|
||||
|
||||
parse(text, opts, massage) {
|
||||
opts = normalizeOptions(opts);
|
||||
const parsed = parser.parse(text, opts);
|
||||
if (massage) {
|
||||
parsed.ast = massageAST(parsed.ast, opts);
|
||||
}
|
||||
return parsed;
|
||||
},
|
||||
|
||||
formatAST(ast, opts) {
|
||||
opts = normalizeOptions(opts);
|
||||
const doc = printAstToDoc(ast, opts);
|
||||
return printDocToString(doc, opts);
|
||||
},
|
||||
|
||||
// Doesn't handle shebang for now
|
||||
formatDoc(doc, opts) {
|
||||
opts = normalizeOptions(opts);
|
||||
const debug = printDocToDebug(doc);
|
||||
return format(debug, opts).formatted;
|
||||
},
|
||||
|
||||
printToDoc(text, opts) {
|
||||
opts = normalizeOptions(opts);
|
||||
const parsed = parser.parse(text, opts);
|
||||
const ast = parsed.ast;
|
||||
text = parsed.text;
|
||||
attachComments(text, ast, opts);
|
||||
return printAstToDoc(ast, opts);
|
||||
},
|
||||
|
||||
printDocToString(doc, opts) {
|
||||
return printDocToString(doc, normalizeOptions(opts));
|
||||
}
|
||||
};
|
|
@ -0,0 +1,218 @@
|
|||
"use strict";
|
||||
|
||||
const comments = require("./comments");
|
||||
|
||||
function findSiblingAncestors(startNodeAndParents, endNodeAndParents, opts) {
|
||||
let resultStartNode = startNodeAndParents.node;
|
||||
let resultEndNode = endNodeAndParents.node;
|
||||
|
||||
if (resultStartNode === resultEndNode) {
|
||||
return {
|
||||
startNode: resultStartNode,
|
||||
endNode: resultEndNode
|
||||
};
|
||||
}
|
||||
|
||||
for (const endParent of endNodeAndParents.parentNodes) {
|
||||
if (
|
||||
endParent.type !== "Program" &&
|
||||
endParent.type !== "File" &&
|
||||
opts.locStart(endParent) >= opts.locStart(startNodeAndParents.node)
|
||||
) {
|
||||
resultEndNode = endParent;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (const startParent of startNodeAndParents.parentNodes) {
|
||||
if (
|
||||
startParent.type !== "Program" &&
|
||||
startParent.type !== "File" &&
|
||||
opts.locEnd(startParent) <= opts.locEnd(endNodeAndParents.node)
|
||||
) {
|
||||
resultStartNode = startParent;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
startNode: resultStartNode,
|
||||
endNode: resultEndNode
|
||||
};
|
||||
}
|
||||
|
||||
function findNodeAtOffset(node, offset, options, predicate, parentNodes) {
|
||||
predicate = predicate || (() => true);
|
||||
parentNodes = parentNodes || [];
|
||||
const start = options.locStart(node, options.locStart);
|
||||
const end = options.locEnd(node, options.locEnd);
|
||||
if (start <= offset && offset <= end) {
|
||||
for (const childNode of comments.getSortedChildNodes(
|
||||
node,
|
||||
undefined /* text */,
|
||||
options
|
||||
)) {
|
||||
const childResult = findNodeAtOffset(
|
||||
childNode,
|
||||
offset,
|
||||
options,
|
||||
predicate,
|
||||
[node].concat(parentNodes)
|
||||
);
|
||||
if (childResult) {
|
||||
return childResult;
|
||||
}
|
||||
}
|
||||
|
||||
if (predicate(node)) {
|
||||
return {
|
||||
node: node,
|
||||
parentNodes: parentNodes
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// See https://www.ecma-international.org/ecma-262/5.1/#sec-A.5
|
||||
function isSourceElement(opts, node) {
|
||||
if (node == null) {
|
||||
return false;
|
||||
}
|
||||
// JS and JS like to avoid repetitions
|
||||
const jsSourceElements = [
|
||||
"FunctionDeclaration",
|
||||
"BlockStatement",
|
||||
"BreakStatement",
|
||||
"ContinueStatement",
|
||||
"DebuggerStatement",
|
||||
"DoWhileStatement",
|
||||
"EmptyStatement",
|
||||
"ExpressionStatement",
|
||||
"ForInStatement",
|
||||
"ForStatement",
|
||||
"IfStatement",
|
||||
"LabeledStatement",
|
||||
"ReturnStatement",
|
||||
"SwitchStatement",
|
||||
"ThrowStatement",
|
||||
"TryStatement",
|
||||
"VariableDeclaration",
|
||||
"WhileStatement",
|
||||
"WithStatement",
|
||||
"ClassDeclaration", // ES 2015
|
||||
"ImportDeclaration", // Module
|
||||
"ExportDefaultDeclaration", // Module
|
||||
"ExportNamedDeclaration", // Module
|
||||
"ExportAllDeclaration", // Module
|
||||
"TypeAlias", // Flow
|
||||
"InterfaceDeclaration", // Flow, TypeScript
|
||||
"TypeAliasDeclaration", // TypeScript
|
||||
"ExportAssignment", // TypeScript
|
||||
"ExportDeclaration" // TypeScript
|
||||
];
|
||||
const jsonSourceElements = [
|
||||
"ObjectExpression",
|
||||
"ArrayExpression",
|
||||
"StringLiteral",
|
||||
"NumericLiteral",
|
||||
"BooleanLiteral",
|
||||
"NullLiteral"
|
||||
];
|
||||
const graphqlSourceElements = [
|
||||
"OperationDefinition",
|
||||
"FragmentDefinition",
|
||||
"VariableDefinition",
|
||||
"TypeExtensionDefinition",
|
||||
"ObjectTypeDefinition",
|
||||
"FieldDefinition",
|
||||
"DirectiveDefinition",
|
||||
"EnumTypeDefinition",
|
||||
"EnumValueDefinition",
|
||||
"InputValueDefinition",
|
||||
"InputObjectTypeDefinition",
|
||||
"SchemaDefinition",
|
||||
"OperationTypeDefinition",
|
||||
"InterfaceTypeDefinition",
|
||||
"UnionTypeDefinition",
|
||||
"ScalarTypeDefinition"
|
||||
];
|
||||
switch (opts.parser) {
|
||||
case "flow":
|
||||
case "babylon":
|
||||
case "typescript":
|
||||
return jsSourceElements.indexOf(node.type) > -1;
|
||||
case "json":
|
||||
return jsonSourceElements.indexOf(node.type) > -1;
|
||||
case "graphql":
|
||||
return graphqlSourceElements.indexOf(node.kind) > -1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function calculateRange(text, opts, ast) {
|
||||
// Contract the range so that it has non-whitespace characters at its endpoints.
|
||||
// This ensures we can format a range that doesn't end on a node.
|
||||
const rangeStringOrig = text.slice(opts.rangeStart, opts.rangeEnd);
|
||||
const startNonWhitespace = Math.max(
|
||||
opts.rangeStart + rangeStringOrig.search(/\S/),
|
||||
opts.rangeStart
|
||||
);
|
||||
let endNonWhitespace;
|
||||
for (
|
||||
endNonWhitespace = opts.rangeEnd;
|
||||
endNonWhitespace > opts.rangeStart;
|
||||
--endNonWhitespace
|
||||
) {
|
||||
if (text[endNonWhitespace - 1].match(/\S/)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const startNodeAndParents = findNodeAtOffset(
|
||||
ast,
|
||||
startNonWhitespace,
|
||||
opts,
|
||||
node => isSourceElement(opts, node)
|
||||
);
|
||||
const endNodeAndParents = findNodeAtOffset(
|
||||
ast,
|
||||
endNonWhitespace,
|
||||
opts,
|
||||
node => isSourceElement(opts, node)
|
||||
);
|
||||
|
||||
if (!startNodeAndParents || !endNodeAndParents) {
|
||||
return {
|
||||
rangeStart: 0,
|
||||
rangeEnd: 0
|
||||
};
|
||||
}
|
||||
|
||||
const siblingAncestors = findSiblingAncestors(
|
||||
startNodeAndParents,
|
||||
endNodeAndParents,
|
||||
opts
|
||||
);
|
||||
const startNode = siblingAncestors.startNode;
|
||||
const endNode = siblingAncestors.endNode;
|
||||
const rangeStart = Math.min(
|
||||
opts.locStart(startNode, opts.locStart),
|
||||
opts.locStart(endNode, opts.locStart)
|
||||
);
|
||||
const rangeEnd = Math.max(
|
||||
opts.locEnd(startNode, opts.locEnd),
|
||||
opts.locEnd(endNode, opts.locEnd)
|
||||
);
|
||||
|
||||
return {
|
||||
rangeStart: rangeStart,
|
||||
rangeEnd: rangeEnd
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
calculateRange,
|
||||
findNodeAtOffset
|
||||
};
|
|
@ -0,0 +1,60 @@
|
|||
"use strict";
|
||||
|
||||
const version = require("./package.json").version;
|
||||
|
||||
const core = require("./src/main/core");
|
||||
const getSupportInfo = require("./src/main/support").getSupportInfo;
|
||||
|
||||
const internalPlugins = [
|
||||
require("./src/language-js"),
|
||||
require("./src/language-css"),
|
||||
require("./src/language-handlebars"),
|
||||
require("./src/language-graphql"),
|
||||
require("./src/language-markdown"),
|
||||
require("./src/language-html"),
|
||||
require("./src/language-vue")
|
||||
];
|
||||
const externalPlugins = {};
|
||||
|
||||
// Luckily `opts` is always the 2nd argument
|
||||
function withPlugins(fn) {
|
||||
return function() {
|
||||
const args = Array.from(arguments);
|
||||
const opts = args[1] || {};
|
||||
args[1] = Object.assign({}, opts, {
|
||||
plugins: internalPlugins.concat(
|
||||
(opts.plugins || [])
|
||||
.map(
|
||||
plugin =>
|
||||
typeof plugin === "string" ? externalPlugins[plugin] : plugin
|
||||
)
|
||||
.filter(Boolean)
|
||||
)
|
||||
});
|
||||
return fn.apply(null, args);
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
registerPlugin(pluginName, pluginBundle) {
|
||||
if (!externalPlugins.hasOwnProperty(pluginName)) {
|
||||
externalPlugins[pluginName] = pluginBundle;
|
||||
}
|
||||
},
|
||||
|
||||
format(text, opts) {
|
||||
return withPlugins(core.formatWithCursor)(text, opts).formatted;
|
||||
},
|
||||
|
||||
getSupportInfo: withPlugins(getSupportInfo),
|
||||
|
||||
version,
|
||||
|
||||
__debug: {
|
||||
parse: withPlugins(core.parse),
|
||||
formatAST: withPlugins(core.formatAST),
|
||||
formatDoc: withPlugins(core.formatDoc),
|
||||
printToDoc: withPlugins(core.printToDoc),
|
||||
printDocToString: withPlugins(core.printDocToString)
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue