fix: get rid of CRLF (#5494)

master
Ika 2018-12-08 18:28:29 +08:00 committed by GitHub
parent b878a54e6a
commit 952bc0cc03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
131 changed files with 2071 additions and 2494 deletions

5
.gitattributes vendored
View File

@ -1,4 +1 @@
# Make sure baseline files have consistent line endings * text=auto eol=lf
*.txt text eol=lf
*.snap text eol=lf
*.debug-check text eol=lf

View File

@ -1,6 +1,6 @@
trigger: trigger:
- master - master
- releases-* - release-*
variables: variables:
AST_COMPARE: true AST_COMPARE: true
@ -31,6 +31,7 @@ jobs:
vmImage: vs2017-win2016 vmImage: vs2017-win2016
variables: variables:
node_version: 10 node_version: 10
TEST_CRLF: true
steps: steps:
- template: .azure-pipelines/dev.yml - template: .azure-pipelines/dev.yml

View File

@ -17,12 +17,8 @@ module.exports = {
"jest-snapshot-serializer-ansi" "jest-snapshot-serializer-ansi"
], ],
testRegex: "jsfmt\\.spec\\.js$|__tests__/.*\\.js$", testRegex: "jsfmt\\.spec\\.js$|__tests__/.*\\.js$",
testPathIgnorePatterns: ["tests/new_react", "tests/more_react"] testPathIgnorePatterns: ["tests/new_react", "tests/more_react"].concat(
.concat(isOldNode ? requiresPrettierInternals : []) isOldNode ? requiresPrettierInternals : []
.concat(
require("os").EOL == "\n"
? ["tests_integration/__tests__/eol-crlf.js"]
: ["tests_integration/__tests__/eol-lf.js"]
), ),
collectCoverage: ENABLE_COVERAGE, collectCoverage: ENABLE_COVERAGE,
collectCoverageFrom: ["src/**/*.js", "index.js", "!<rootDir>/node_modules/"], collectCoverageFrom: ["src/**/*.js", "index.js", "!<rootDir>/node_modules/"],

View File

@ -71,8 +71,8 @@
"unicode-regex": "2.0.0", "unicode-regex": "2.0.0",
"unified": "6.1.6", "unified": "6.1.6",
"vnopts": "1.0.2", "vnopts": "1.0.2",
"yaml": "1.0.0-rc.8", "yaml": "1.0.2",
"yaml-unist-parser": "1.0.0-rc.4" "yaml-unist-parser": "1.0.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "7.1.5", "@babel/cli": "7.1.5",

View File

@ -675,7 +675,19 @@ function isWithinParentArrayProperty(path, propertyName) {
return parent[propertyName][key] === node; return parent[propertyName][key] === node;
} }
function replaceEndOfLineWith(text, replacement) {
const parts = [];
for (const part of text.split("\n")) {
if (parts.length !== 0) {
parts.push(replacement);
}
parts.push(part);
}
return parts;
}
module.exports = { module.exports = {
replaceEndOfLineWith,
getStringWidth, getStringWidth,
getMaxContinuousCount, getMaxContinuousCount,
getPrecedence, getPrecedence,

View File

@ -110,11 +110,7 @@ function genericPrint(path, options, print) {
} }
case "css-comment": { case "css-comment": {
if (node.raws.content) { if (node.raws.content) {
return ( return node.raws.content;
node.raws.content
// there's a bug in the less parser that trailing `\r`s are included in inline comments
.replace(/^(\/\/[^]+)\r+$/, "$1")
);
} }
const text = options.originalText.slice( const text = options.originalText.slice(
options.locStart(node), options.locStart(node),

View File

@ -265,7 +265,6 @@ function createParser({
allowHtmComponentClosingTags = false allowHtmComponentClosingTags = false
} = {}) { } = {}) {
return { return {
preprocess: text => text.replace(/\r\n?/g, "\n"),
parse: (text, parsers, options) => parse: (text, parsers, options) =>
_parse(text, options, { _parse(text, options, {
recognizeSelfClosing, recognizeSelfClosing,

View File

@ -34,12 +34,11 @@ const {
isTextLikeNode, isTextLikeNode,
normalizeParts, normalizeParts,
preferHardlineAsLeadingSpaces, preferHardlineAsLeadingSpaces,
replaceDocNewlines,
replaceNewlines,
shouldNotPrintClosingTag, shouldNotPrintClosingTag,
shouldPreserveContent, shouldPreserveContent,
unescapeQuoteEntities unescapeQuoteEntities
} = require("./utils"); } = require("./utils");
const { replaceEndOfLineWith } = require("../common/util");
const preprocess = require("./preprocess"); const preprocess = require("./preprocess");
const assert = require("assert"); const assert = require("assert");
const { insertPragma } = require("./pragma"); const { insertPragma } = require("./pragma");
@ -147,10 +146,7 @@ function embed(path, print, textToDoc, options) {
hardline, hardline,
node.value.trim().length === 0 node.value.trim().length === 0
? "" ? ""
: replaceDocNewlines( : textToDoc(node.value, { parser: "yaml" }),
textToDoc(node.value, { parser: "yaml" }),
literalline
),
"---" "---"
]) ])
); );
@ -287,7 +283,7 @@ function genericPrint(path, options, print) {
? node.value.replace(trailingNewlineRegex, "") ? node.value.replace(trailingNewlineRegex, "")
: node.value; : node.value;
return concat([ return concat([
concat(replaceNewlines(value, literalline)), concat(replaceEndOfLineWith(value, literalline)),
hasTrailingNewline ? hardline : "" hasTrailingNewline ? hardline : ""
]); ]);
} }
@ -316,7 +312,7 @@ function genericPrint(path, options, print) {
return concat([ return concat([
printOpeningTagPrefix(node, options), printOpeningTagPrefix(node, options),
concat( concat(
replaceNewlines( replaceEndOfLineWith(
options.originalText.slice( options.originalText.slice(
options.locStart(node), options.locStart(node),
options.locEnd(node) options.locEnd(node)
@ -341,7 +337,7 @@ function genericPrint(path, options, print) {
"=", "=",
quote, quote,
concat( concat(
replaceNewlines( replaceEndOfLineWith(
quote === '"' quote === '"'
? value.replace(/"/g, "&quot;") ? value.replace(/"/g, "&quot;")
: value.replace(/'/g, "&apos;"), : value.replace(/'/g, "&apos;"),
@ -354,7 +350,7 @@ function genericPrint(path, options, print) {
} }
case "yaml": case "yaml":
case "toml": case "toml":
return node.raw; return concat(replaceEndOfLineWith(node.raw, literalline));
default: default:
throw new Error(`Unexpected node type ${node.type}`); throw new Error(`Unexpected node type ${node.type}`);
} }
@ -473,7 +469,7 @@ function printChildren(path, options, print) {
return concat( return concat(
[].concat( [].concat(
printOpeningTagPrefix(child, options), printOpeningTagPrefix(child, options),
replaceNewlines( replaceEndOfLineWith(
options.originalText.slice( options.originalText.slice(
options.locStart(child) + options.locStart(child) +
(child.prev && (child.prev &&
@ -497,7 +493,7 @@ function printChildren(path, options, print) {
[].concat( [].concat(
printOpeningTagPrefix(child, options), printOpeningTagPrefix(child, options),
group(printOpeningTag(childPath, options, print)), group(printOpeningTag(childPath, options, print)),
replaceNewlines( replaceEndOfLineWith(
options.originalText.slice( options.originalText.slice(
child.startSourceSpan.end.offset + child.startSourceSpan.end.offset +
(child.firstChild && (child.firstChild &&
@ -620,7 +616,7 @@ function printOpeningTag(path, options, print) {
const attr = attrPath.getValue(); const attr = attrPath.getValue();
return hasPrettierIgnoreAttribute(attr) return hasPrettierIgnoreAttribute(attr)
? concat( ? concat(
replaceNewlines( replaceEndOfLineWith(
options.originalText.slice( options.originalText.slice(
options.locStart(attr), options.locStart(attr),
options.locEnd(attr) options.locEnd(attr)
@ -894,8 +890,8 @@ function printClosingTagEndMarker(node, options) {
function getTextValueParts(node, value = node.value) { function getTextValueParts(node, value = node.value) {
return node.parent.isWhitespaceSensitive return node.parent.isWhitespaceSensitive
? node.parent.isIndentationSensitive ? node.parent.isIndentationSensitive
? replaceNewlines(value, literalline) ? replaceEndOfLineWith(value, literalline)
: replaceNewlines( : replaceEndOfLineWith(
dedentString(value.replace(/^\s*?\n|\n\s*?$/g, "")), dedentString(value.replace(/^\s*?\n|\n\s*?$/g, "")),
hardline hardline
) )
@ -1037,7 +1033,7 @@ function printEmbeddedAttributeValue(node, originalTextToDoc, options) {
const parts = []; const parts = [];
value.split(interpolationRegex).forEach((part, index) => { value.split(interpolationRegex).forEach((part, index) => {
if (index % 2 === 0) { if (index % 2 === 0) {
parts.push(concat(replaceNewlines(part, literalline))); parts.push(concat(replaceEndOfLineWith(part, literalline)));
} else { } else {
try { try {
parts.push( parts.push(
@ -1056,7 +1052,11 @@ function printEmbeddedAttributeValue(node, originalTextToDoc, options) {
) )
); );
} catch (e) { } catch (e) {
parts.push("{{", concat(replaceNewlines(part, literalline)), "}}"); parts.push(
"{{",
concat(replaceEndOfLineWith(part, literalline)),
"}}"
);
} }
} }
}); });

View File

@ -1,10 +1,5 @@
"use strict"; "use strict";
const {
builders: { concat },
utils: { mapDoc }
} = require("../doc");
const { const {
CSS_DISPLAY_TAGS, CSS_DISPLAY_TAGS,
CSS_DISPLAY_DEFAULT, CSS_DISPLAY_DEFAULT,
@ -263,20 +258,6 @@ function isDanglingSpaceSensitiveNode(node) {
); );
} }
function replaceNewlines(text, replacement) {
return text
.split(/(\n)/g)
.map((data, index) => (index % 2 === 1 ? replacement : data));
}
function replaceDocNewlines(doc, replacement) {
return mapDoc(doc, currentDoc =>
typeof currentDoc === "string" && currentDoc.includes("\n")
? concat(replaceNewlines(currentDoc, replacement))
: currentDoc
);
}
function forceNextEmptyLine(node) { function forceNextEmptyLine(node) {
return ( return (
isFrontMatterNode(node) || isFrontMatterNode(node) ||
@ -645,8 +626,6 @@ module.exports = {
normalizeParts, normalizeParts,
preferHardlineAsLeadingSpaces, preferHardlineAsLeadingSpaces,
preferHardlineAsTrailingSpaces, preferHardlineAsTrailingSpaces,
replaceDocNewlines,
replaceNewlines,
shouldNotPrintClosingTag, shouldNotPrintClosingTag,
shouldPreserveContent, shouldPreserveContent,
unescapeQuoteEntities unescapeQuoteEntities

View File

@ -2395,7 +2395,10 @@ function printPathNoParens(path, options, print, args) {
"${" + "${" +
printDocToString( printDocToString(
doc, doc,
Object.assign({}, options, { printWidth: Infinity }) Object.assign({}, options, {
printWidth: Infinity,
endOfLine: "lf"
})
).formatted + ).formatted +
"}" "}"
); );

View File

@ -42,7 +42,7 @@ function embed(path, print, textToDoc, options) {
concat([ concat([
"---", "---",
hardline, hardline,
node.value.trim() node.value && node.value.trim()
? replaceNewlinesWithLiterallines( ? replaceNewlinesWithLiterallines(
textToDoc(node.value, { parser: "yaml" }) textToDoc(node.value, { parser: "yaml" })
) )

View File

@ -9,7 +9,7 @@ function startWithPragma(text) {
const regex = new RegExp( const regex = new RegExp(
[ [
`<!--\\s*${pragma}\\s*-->`, `<!--\\s*${pragma}\\s*-->`,
`<!--.*\n[\\s\\S]*(^|\n)[^\\S\n]*${pragma}[^\\S\n]*($|\n)[\\s\\S]*\n.*-->` `<!--.*\r?\n[\\s\\S]*(^|\n)[^\\S\n]*${pragma}[^\\S\n]*($|\n)[\\s\\S]*\n.*-->`
].join("|"), ].join("|"),
"m" "m"
); );

View File

@ -27,6 +27,7 @@ const {
splitText, splitText,
punctuationPattern punctuationPattern
} = require("./utils"); } = require("./utils");
const { replaceEndOfLineWith } = require("../common/util");
const TRAILING_HARDLINE_NODES = ["importExport"]; const TRAILING_HARDLINE_NODES = ["importExport"];
@ -209,7 +210,10 @@ function genericPrint(path, options, print) {
const alignment = " ".repeat(4); const alignment = " ".repeat(4);
return align( return align(
alignment, alignment,
concat([alignment, replaceNewlinesWith(node.value, hardline)]) concat([
alignment,
concat(replaceEndOfLineWith(node.value, hardline))
])
); );
} }
@ -225,9 +229,11 @@ function genericPrint(path, options, print) {
style, style,
node.lang || "", node.lang || "",
hardline, hardline,
replaceNewlinesWith( concat(
replaceEndOfLineWith(
getFencedCodeBlockValue(node, options.originalText), getFencedCodeBlockValue(node, options.originalText),
hardline hardline
)
), ),
hardline, hardline,
style style
@ -247,9 +253,11 @@ function genericPrint(path, options, print) {
? node.value.trimRight() ? node.value.trimRight()
: node.value; : node.value;
const isHtmlComment = /^<!--[\s\S]*-->$/.test(value); const isHtmlComment = /^<!--[\s\S]*-->$/.test(value);
return replaceNewlinesWith( return concat(
replaceEndOfLineWith(
value, value,
isHtmlComment ? hardline : markAsRoot(literalline) isHtmlComment ? hardline : markAsRoot(literalline)
)
); );
} }
case "list": { case "list": {
@ -394,7 +402,7 @@ function genericPrint(path, options, print) {
? concat([" ", markAsRoot(literalline)]) ? concat([" ", markAsRoot(literalline)])
: concat(["\\", hardline]); : concat(["\\", hardline]);
case "liquidNode": case "liquidNode":
return replaceNewlinesWith(node.value, hardline); return concat(replaceEndOfLineWith(node.value, hardline));
// MDX // MDX
case "importExport": case "importExport":
case "jsx": case "jsx":
@ -404,7 +412,10 @@ function genericPrint(path, options, print) {
"$$", "$$",
hardline, hardline,
node.value node.value
? concat([replaceNewlinesWith(node.value, hardline), hardline]) ? concat([
concat(replaceEndOfLineWith(node.value, hardline)),
hardline
])
: "", : "",
"$$" "$$"
]); ]);
@ -467,10 +478,6 @@ function getNthListSiblingIndex(node, parentNode) {
); );
} }
function replaceNewlinesWith(str, doc) {
return join(doc, str.replace(/\r\n?/g, "\n").split("\n"));
}
function getNthSiblingIndex(node, parentNode, condition) { function getNthSiblingIndex(node, parentNode, condition) {
condition = condition || (() => true); condition = condition || (() => true);

View File

@ -148,7 +148,7 @@ function getFencedCodeBlockValue(node, originalText) {
const leadingSpaceCount = text.match(/^\s*/)[0].length; const leadingSpaceCount = text.match(/^\s*/)[0].length;
const replaceRegex = new RegExp(`^\\s{0,${leadingSpaceCount}}`); const replaceRegex = new RegExp(`^\\s{0,${leadingSpaceCount}}`);
const lineContents = text.replace(/\r\n?/g, "\n").split("\n"); const lineContents = text.split("\n");
const markerStyle = text[leadingSpaceCount]; // ` or ~ const markerStyle = text[leadingSpaceCount]; // ` or ~
const marker = text const marker = text

View File

@ -29,11 +29,7 @@ const parser = {
parse, parse,
hasPragma, hasPragma,
locStart: node => node.position.start.offset, locStart: node => node.position.start.offset,
locEnd: node => node.position.end.offset, locEnd: node => node.position.end.offset
// workaround for https://github.com/eemeli/yaml/issues/20
preprocess: text =>
text.indexOf("\r") === -1 ? text : text.replace(/\r\n?/g, "\n")
}; };
module.exports = { module.exports = {

View File

@ -38,6 +38,7 @@ const {
markAsRoot, markAsRoot,
softline softline
} = docBuilders; } = docBuilders;
const { replaceEndOfLineWith } = require("../common/util");
function preprocess(ast) { function preprocess(ast) {
return mapNode(ast, defineShortcuts); return mapNode(ast, defineShortcuts);
@ -104,9 +105,14 @@ function genericPrint(path, options, print) {
]) ])
: "", : "",
hasPrettierIgnore(path) hasPrettierIgnore(path)
? options.originalText.slice( ? concat(
replaceEndOfLineWith(
options.originalText.slice(
node.position.start.offset, node.position.start.offset,
node.position.end.offset node.position.end.offset
),
literalline
)
) )
: group(_print(node, parentNode, path, options, print)), : group(_print(node, parentNode, path, options, print)),
hasTrailingComment(node) && !isNode(node, ["document", "documentHead"]) hasTrailingComment(node) && !isNode(node, ["document", "documentHead"])

View File

@ -1,8 +1,6 @@
"use strict"; "use strict";
function getLast(array) { const { getLast } = require("../common/util");
return array[array.length - 1];
}
function getAncestorCount(path, filter) { function getAncestorCount(path, filter) {
let counter = 0; let counter = 0;

View File

@ -14,6 +14,7 @@ const {
const rangeUtil = require("./range-util"); const rangeUtil = require("./range-util");
const privateUtil = require("../common/util"); const privateUtil = require("../common/util");
const { const {
utils: { mapDoc },
printer: { printDocToString }, printer: { printDocToString },
debug: { printDocToDebug } debug: { printDocToDebug }
} = require("../doc"); } = require("../doc");
@ -21,6 +22,11 @@ const {
const UTF8BOM = 0xfeff; const UTF8BOM = 0xfeff;
const CURSOR = Symbol("cursor"); const CURSOR = Symbol("cursor");
const PLACEHOLDERS = {
cursorOffset: "<<<PRETTIER_CURSOR>>>",
rangeStart: "<<<PRETTIER_RANGE_START>>>",
rangeEnd: "<<<PRETTIER_RANGE_END>>>"
};
function ensureAllCommentsPrinted(astComments) { function ensureAllCommentsPrinted(astComments) {
if (!astComments) { if (!astComments) {
@ -67,8 +73,6 @@ function coreFormat(text, opts, addAlignmentSize) {
const parsed = parser.parse(text, opts); const parsed = parser.parse(text, opts);
const ast = parsed.ast; const ast = parsed.ast;
const originalText = text;
text = parsed.text; text = parsed.text;
if (opts.cursorOffset >= 0) { if (opts.cursorOffset >= 0) {
@ -80,11 +84,18 @@ function coreFormat(text, opts, addAlignmentSize) {
const astComments = attachComments(text, ast, opts); const astComments = attachComments(text, ast, opts);
const doc = printAstToDoc(ast, opts, addAlignmentSize); const doc = printAstToDoc(ast, opts, addAlignmentSize);
if (opts.endOfLine === "auto") {
opts.endOfLine = guessEndOfLine(originalText);
}
const result = printDocToString(doc, opts); const eol = convertEndOfLineToChars(opts.endOfLine);
const result = printDocToString(
opts.endOfLine === "lf"
? doc
: mapDoc(doc, currentDoc =>
typeof currentDoc === "string" && currentDoc.indexOf("\n") !== -1
? currentDoc.replace(/\n/g, eol)
: currentDoc
),
opts
);
ensureAllCommentsPrinted(astComments); ensureAllCommentsPrinted(astComments);
// Remove extra leading indentation as well as the added indentation after last newline // Remove extra leading indentation as well as the added indentation after last newline
@ -212,8 +223,8 @@ function formatRange(text, opts) {
// Since the range contracts to avoid trailing whitespace, // Since the range contracts to avoid trailing whitespace,
// we need to remove the newline that was inserted by the `format` call. // we need to remove the newline that was inserted by the `format` call.
const rangeTrimmed = rangeResult.formatted.trimRight(); const rangeTrimmed = rangeResult.formatted.trimRight();
const formatted = const rangeLeft = text.slice(0, rangeStart);
text.slice(0, rangeStart) + rangeTrimmed + text.slice(rangeEnd); const rangeRight = text.slice(rangeEnd);
let cursorOffset = opts.cursorOffset; let cursorOffset = opts.cursorOffset;
if (opts.cursorOffset >= rangeEnd) { if (opts.cursorOffset >= rangeEnd) {
@ -226,6 +237,44 @@ function formatRange(text, opts) {
} }
// keep the cursor as it was if it was before the start of the range // keep the cursor as it was if it was before the start of the range
let formatted;
if (opts.endOfLine === "lf") {
formatted = rangeLeft + rangeTrimmed + rangeRight;
} else {
const eol = convertEndOfLineToChars(opts.endOfLine);
if (cursorOffset >= 0) {
const parts = [rangeLeft, rangeTrimmed, rangeRight];
let partIndex = 0;
let partOffset = cursorOffset;
while (partIndex < parts.length) {
const part = parts[partIndex];
if (partOffset < part.length) {
parts[partIndex] =
parts[partIndex].slice(0, partOffset) +
PLACEHOLDERS.cursorOffset +
parts[partIndex].slice(partOffset);
break;
}
partIndex++;
partOffset -= part.length;
}
const [newRangeLeft, newRangeTrimmed, newRangeRight] = parts;
formatted = (
newRangeLeft.replace(/\n/g, eol) +
newRangeTrimmed +
newRangeRight.replace(/\n/g, eol)
).replace(PLACEHOLDERS.cursorOffset, (_, index) => {
cursorOffset = index;
return "";
});
} else {
formatted =
rangeLeft.replace(/\n/g, eol) +
rangeTrimmed +
rangeRight.replace(/\n/g, eol);
}
}
return { formatted, cursorOffset }; return { formatted, cursorOffset };
} }
@ -236,23 +285,83 @@ function format(text, opts) {
return { formatted: text }; return { formatted: text };
} }
if (opts.rangeStart > 0 || opts.rangeEnd < text.length) { if (opts.endOfLine === "auto") {
return formatRange(text, opts); opts.endOfLine = guessEndOfLine(text);
}
const hasCursor = opts.cursorOffset >= 0;
const hasRangeStart = opts.rangeStart > 0;
const hasRangeEnd = opts.rangeEnd < text.length;
// get rid of CR/CRLF parsing
if (text.indexOf("\r") !== -1) {
const offsetKeys = [
hasCursor && "cursorOffset",
hasRangeStart && "rangeStart",
hasRangeEnd && "rangeEnd"
]
.filter(Boolean)
.sort((aKey, bKey) => opts[aKey] - opts[bKey]);
for (let i = offsetKeys.length - 1; i >= 0; i--) {
const key = offsetKeys[i];
text =
text.slice(0, opts[key]) + PLACEHOLDERS[key] + text.slice(opts[key]);
}
text = text.replace(/\r\n?/g, "\n");
for (let i = 0; i < offsetKeys.length; i++) {
const key = offsetKeys[i];
text = text.replace(PLACEHOLDERS[key], (_, index) => {
opts[key] = index;
return "";
});
}
} }
const hasUnicodeBOM = text.charCodeAt(0) === UTF8BOM; const hasUnicodeBOM = text.charCodeAt(0) === UTF8BOM;
if (hasUnicodeBOM) { if (hasUnicodeBOM) {
text = text.substring(1); text = text.substring(1);
if (hasCursor) {
opts.cursorOffset++;
}
if (hasRangeStart) {
opts.rangeStart++;
}
if (hasRangeEnd) {
opts.rangeEnd++;
}
} }
if (opts.insertPragma && opts.printer.insertPragma && !hasPragma) { if (!hasCursor) {
text = opts.printer.insertPragma(text); opts.cursorOffset = -1;
}
if (opts.rangeStart < 0) {
opts.rangeStart = 0;
}
if (opts.rangeEnd > text.length) {
opts.rangeEnd = text.length;
} }
const result = coreFormat(text, opts); const result =
hasRangeStart || hasRangeEnd
? formatRange(text, opts)
: coreFormat(
opts.insertPragma && opts.printer.insertPragma && !hasPragma
? opts.printer.insertPragma(text)
: text,
opts
);
if (hasUnicodeBOM) { if (hasUnicodeBOM) {
result.formatted = String.fromCharCode(UTF8BOM) + result.formatted; result.formatted = String.fromCharCode(UTF8BOM) + result.formatted;
if (hasCursor) {
result.cursorOffset++;
} }
}
return result; return result;
} }
@ -264,6 +373,9 @@ module.exports = {
parse(text, opts, massage) { parse(text, opts, massage) {
opts = normalizeOptions(opts); opts = normalizeOptions(opts);
if (text.indexOf("\r") !== -1) {
text = text.replace(/\r\n?/g, "\n");
}
const parsed = parser.parse(text, opts); const parsed = parser.parse(text, opts);
if (massage) { if (massage) {
parsed.ast = massageAST(parsed.ast, opts); parsed.ast = massageAST(parsed.ast, opts);

View File

@ -1,145 +1,58 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`usingCrlf.js 1`] = ` exports[`example.js 1`] = `
====================================options=====================================
parsers: ["babylon"]
printWidth: 80
| printWidth
=====================================input======================================
function f() {
console.log("testing line endings");
}
=====================================output=====================================
function f() {
console.log("testing line endings");
}
================================================================================
`;
exports[`usingCrlf.js 2`] = `
====================================options===================================== ====================================options=====================================
endOfLine: "cr" endOfLine: "cr"
parsers: ["babylon"] parsers: ["babylon"]
printWidth: 80 printWidth: 80
| printWidth | printWidth
=====================================input====================================== =====================================input======================================
function f() { function f() {<LF>
console.log("testing line endings"); console.log("testing line endings");<LF>
} }<LF>
=====================================output===================================== =====================================output=====================================
function f() {/*CR*/ console.log("testing line endings");/*CR*/}/*CR*/ function f() {<CR>
console.log("testing line endings");<CR>
}<CR>
================================================================================ ================================================================================
`; `;
exports[`usingCrlf.js 3`] = ` exports[`example.js 2`] = `
====================================options===================================== ====================================options=====================================
endOfLine: "crlf" endOfLine: "crlf"
parsers: ["babylon"] parsers: ["babylon"]
printWidth: 80 printWidth: 80
| printWidth | printWidth
=====================================input====================================== =====================================input======================================
function f() { function f() {<LF>
console.log("testing line endings"); console.log("testing line endings");<LF>
} }<LF>
=====================================output===================================== =====================================output=====================================
function f() {/*CR*/ function f() {<CRLF>
console.log("testing line endings");/*CR*/ console.log("testing line endings");<CRLF>
}/*CR*/ }<CRLF>
================================================================================ ================================================================================
`; `;
exports[`usingCrlf.js 4`] = ` exports[`example.js 3`] = `
====================================options===================================== ====================================options=====================================
endOfLine: "lf" endOfLine: "lf"
parsers: ["babylon"] parsers: ["babylon"]
printWidth: 80 printWidth: 80
| printWidth | printWidth
=====================================input====================================== =====================================input======================================
function f() { function f() {<LF>
console.log("testing line endings"); console.log("testing line endings");<LF>
} }<LF>
=====================================output===================================== =====================================output=====================================
function f() { function f() {<LF>
console.log("testing line endings"); console.log("testing line endings");<LF>
} }<LF>
================================================================================
`;
exports[`usingLf.js 1`] = `
====================================options=====================================
parsers: ["babylon"]
printWidth: 80
| printWidth
=====================================input======================================
function f() {
console.log("testing line endings");
}
=====================================output=====================================
function f() {
console.log("testing line endings");
}
================================================================================
`;
exports[`usingLf.js 2`] = `
====================================options=====================================
endOfLine: "cr"
parsers: ["babylon"]
printWidth: 80
| printWidth
=====================================input======================================
function f() {
console.log("testing line endings");
}
=====================================output=====================================
function f() {/*CR*/ console.log("testing line endings");/*CR*/}/*CR*/
================================================================================
`;
exports[`usingLf.js 3`] = `
====================================options=====================================
endOfLine: "crlf"
parsers: ["babylon"]
printWidth: 80
| printWidth
=====================================input======================================
function f() {
console.log("testing line endings");
}
=====================================output=====================================
function f() {/*CR*/
console.log("testing line endings");/*CR*/
}/*CR*/
================================================================================
`;
exports[`usingLf.js 4`] = `
====================================options=====================================
endOfLine: "lf"
parsers: ["babylon"]
printWidth: 80
| printWidth
=====================================input======================================
function f() {
console.log("testing line endings");
}
=====================================output=====================================
function f() {
console.log("testing line endings");
}
================================================================================ ================================================================================
`; `;

View File

@ -1,4 +1,3 @@
run_spec(__dirname, ["babylon"]);
run_spec(__dirname, ["babylon"], { endOfLine: "cr" }); run_spec(__dirname, ["babylon"], { endOfLine: "cr" });
run_spec(__dirname, ["babylon"], { endOfLine: "crlf" }); run_spec(__dirname, ["babylon"], { endOfLine: "crlf" });
run_spec(__dirname, ["babylon"], { endOfLine: "lf" }); run_spec(__dirname, ["babylon"], { endOfLine: "lf" });

View File

@ -1,3 +0,0 @@
function f() {
console.log("testing line endings");
}

View File

@ -1,137 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`usingCrlf.css 1`] = `
====================================options=====================================
parsers: ["css"]
printWidth: 80
| printWidth
=====================================input======================================
.foo {
margin: 42px;
}
=====================================output=====================================
.foo {
margin: 42px;
}
================================================================================
`;
exports[`usingCrlf.css 2`] = `
====================================options=====================================
endOfLine: "cr"
parsers: ["css"]
printWidth: 80
| printWidth
=====================================input======================================
.foo {
margin: 42px;
}
=====================================output=====================================
.foo {/*CR*/ margin: 42px;/*CR*/}/*CR*/
================================================================================
`;
exports[`usingCrlf.css 3`] = `
====================================options=====================================
endOfLine: "crlf"
parsers: ["css"]
printWidth: 80
| printWidth
=====================================input======================================
.foo {
margin: 42px;
}
=====================================output=====================================
.foo {/*CR*/
margin: 42px;/*CR*/
}/*CR*/
================================================================================
`;
exports[`usingCrlf.css 4`] = `
====================================options=====================================
endOfLine: "lf"
parsers: ["css"]
printWidth: 80
| printWidth
=====================================input======================================
.foo {
margin: 42px;
}
=====================================output=====================================
.foo {
margin: 42px;
}
================================================================================
`;
exports[`usingLf.css 1`] = `
====================================options=====================================
parsers: ["css"]
printWidth: 80
| printWidth
=====================================input======================================
.foo {
margin: 42px;
}
=====================================output=====================================
.foo {
margin: 42px;
}
================================================================================
`;
exports[`usingLf.css 2`] = `
====================================options=====================================
endOfLine: "cr"
parsers: ["css"]
printWidth: 80
| printWidth
=====================================input======================================
.foo {
margin: 42px;
}
=====================================output=====================================
.foo {/*CR*/ margin: 42px;/*CR*/}/*CR*/
================================================================================
`;
exports[`usingLf.css 3`] = `
====================================options=====================================
endOfLine: "crlf"
parsers: ["css"]
printWidth: 80
| printWidth
=====================================input======================================
.foo {
margin: 42px;
}
=====================================output=====================================
.foo {/*CR*/
margin: 42px;/*CR*/
}/*CR*/
================================================================================
`;
exports[`usingLf.css 4`] = `
====================================options=====================================
endOfLine: "lf"
parsers: ["css"]
printWidth: 80
| printWidth
=====================================input======================================
.foo {
margin: 42px;
}
=====================================output=====================================
.foo {
margin: 42px;
}
================================================================================
`;

View File

@ -1,4 +0,0 @@
run_spec(__dirname, ["css"]);
run_spec(__dirname, ["css"], { endOfLine: "cr" });
run_spec(__dirname, ["css"], { endOfLine: "crlf" });
run_spec(__dirname, ["css"], { endOfLine: "lf" });

View File

@ -1,3 +0,0 @@
.foo {
margin: 42px;
}

View File

@ -1,3 +0,0 @@
.foo {
margin: 42px;
}

View File

@ -1,137 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`usingCrlf.md 1`] = `
====================================options=====================================
parsers: ["markdown"]
printWidth: 80
| printWidth
=====================================input======================================
# Markdown file
testing line endings
=====================================output=====================================
# Markdown file
testing line endings
================================================================================
`;
exports[`usingCrlf.md 2`] = `
====================================options=====================================
endOfLine: "cr"
parsers: ["markdown"]
printWidth: 80
| printWidth
=====================================input======================================
# Markdown file
testing line endings
=====================================output=====================================
# Markdown file/*CR*//*CR*/testing line endings/*CR*/
================================================================================
`;
exports[`usingCrlf.md 3`] = `
====================================options=====================================
endOfLine: "crlf"
parsers: ["markdown"]
printWidth: 80
| printWidth
=====================================input======================================
# Markdown file
testing line endings
=====================================output=====================================
# Markdown file/*CR*/
/*CR*/
testing line endings/*CR*/
================================================================================
`;
exports[`usingCrlf.md 4`] = `
====================================options=====================================
endOfLine: "lf"
parsers: ["markdown"]
printWidth: 80
| printWidth
=====================================input======================================
# Markdown file
testing line endings
=====================================output=====================================
# Markdown file
testing line endings
================================================================================
`;
exports[`usingLf.md 1`] = `
====================================options=====================================
parsers: ["markdown"]
printWidth: 80
| printWidth
=====================================input======================================
# Markdown file
testing line endings
=====================================output=====================================
# Markdown file
testing line endings
================================================================================
`;
exports[`usingLf.md 2`] = `
====================================options=====================================
endOfLine: "cr"
parsers: ["markdown"]
printWidth: 80
| printWidth
=====================================input======================================
# Markdown file
testing line endings
=====================================output=====================================
# Markdown file/*CR*//*CR*/testing line endings/*CR*/
================================================================================
`;
exports[`usingLf.md 3`] = `
====================================options=====================================
endOfLine: "crlf"
parsers: ["markdown"]
printWidth: 80
| printWidth
=====================================input======================================
# Markdown file
testing line endings
=====================================output=====================================
# Markdown file/*CR*/
/*CR*/
testing line endings/*CR*/
================================================================================
`;
exports[`usingLf.md 4`] = `
====================================options=====================================
endOfLine: "lf"
parsers: ["markdown"]
printWidth: 80
| printWidth
=====================================input======================================
# Markdown file
testing line endings
=====================================output=====================================
# Markdown file
testing line endings
================================================================================
`;

View File

@ -1,4 +0,0 @@
run_spec(__dirname, ["markdown"]);
run_spec(__dirname, ["markdown"], { endOfLine: "cr" });
run_spec(__dirname, ["markdown"], { endOfLine: "crlf" });
run_spec(__dirname, ["markdown"], { endOfLine: "lf" });

View File

@ -1,3 +0,0 @@
# Markdown file
testing line endings

View File

@ -1,3 +0,0 @@
# Markdown file
testing line endings

View File

@ -1,59 +1,61 @@
"use strict"; "use strict";
const fs = require("fs"); const fs = require("fs");
const extname = require("path").extname; const path = require("path");
const raw = require("jest-snapshot-serializer-raw").wrap; const raw = require("jest-snapshot-serializer-raw").wrap;
const AST_COMPARE = process.env["AST_COMPARE"]; const AST_COMPARE = process.env["AST_COMPARE"];
const TEST_STANDALONE = process.env["TEST_STANDALONE"]; const TEST_STANDALONE = process.env["TEST_STANDALONE"];
const TEST_CRLF = process.env["TEST_CRLF"];
const CURSOR_PLACEHOLDER = "<|>";
const RANGE_START_PLACEHOLDER = "<<<PRETTIER_RANGE_START>>>";
const RANGE_END_PLACEHOLDER = "<<<PRETTIER_RANGE_END>>>";
const prettier = !TEST_STANDALONE const prettier = !TEST_STANDALONE
? require("prettier/local") ? require("prettier/local")
: require("prettier/standalone"); : require("prettier/standalone");
function run_spec(dirname, parsers, options) { global.run_spec = (dirname, parsers, options) => {
/* instabul ignore if */ // istanbul ignore next
if (!parsers || !parsers.length) { if (!parsers || !parsers.length) {
throw new Error(`No parsers were specified for ${dirname}`); throw new Error(`No parsers were specified for ${dirname}`);
} }
fs.readdirSync(dirname).forEach(filename => { fs.readdirSync(dirname).forEach(basename => {
// We need to have a skipped test with the same name of the snapshots, const filename = path.join(dirname, basename);
// so Jest doesn't mark them as obsolete.
if (TEST_STANDALONE && parsers.some(skipStandalone)) { if (
parsers.forEach(parser => path.extname(basename) === ".snap" ||
test.skip(`${filename} - ${parser}-verify`, () => {}) !fs.lstatSync(filename).isFile() ||
); basename[0] === "." ||
basename === "jsfmt.spec.js"
) {
return; return;
} }
const path = dirname + "/" + filename;
if (
extname(filename) !== ".snap" &&
fs.lstatSync(path).isFile() &&
filename[0] !== "." &&
filename !== "jsfmt.spec.js"
) {
let rangeStart; let rangeStart;
let rangeEnd; let rangeEnd;
let cursorOffset; let cursorOffset;
const source = read(path)
.replace(/\r\n/g, "\n") const text = fs.readFileSync(filename, "utf8");
.replace("<<<PRETTIER_RANGE_START>>>", (match, offset) => {
const source = (TEST_CRLF ? text.replace(/\n/g, "\r\n") : text)
.replace(RANGE_START_PLACEHOLDER, (match, offset) => {
rangeStart = offset; rangeStart = offset;
return ""; return "";
}) })
.replace("<<<PRETTIER_RANGE_END>>>", (match, offset) => { .replace(RANGE_END_PLACEHOLDER, (match, offset) => {
rangeEnd = offset; rangeEnd = offset;
return ""; return "";
}); });
const input = source.replace("<|>", (match, offset) => { const input = source.replace(CURSOR_PLACEHOLDER, (match, offset) => {
cursorOffset = offset; cursorOffset = offset;
return ""; return "";
}); });
const baseOptions = Object.assign(mergeDefaultOptions(options || {}), { const baseOptions = Object.assign({ printWidth: 80 }, options, {
rangeStart, rangeStart,
rangeEnd, rangeEnd,
cursorOffset cursorOffset
@ -61,97 +63,101 @@ function run_spec(dirname, parsers, options) {
const mainOptions = Object.assign({}, baseOptions, { const mainOptions = Object.assign({}, baseOptions, {
parser: parsers[0] parser: parsers[0]
}); });
const output = prettyprint(input, path, mainOptions);
test(filename, () => { const hasEndOfLine = "endOfLine" in mainOptions;
const output = format(input, filename, mainOptions);
const visualizedOutput = visualizeEndOfLine(output);
test(basename, () => {
expect(visualizedOutput).toEqual(
visualizeEndOfLine(consistentEndOfLine(output))
);
expect( expect(
raw( raw(
createSnapshot( createSnapshot(
source, hasEndOfLine
output, ? visualizeEndOfLine(
text
.replace(RANGE_START_PLACEHOLDER, "")
.replace(RANGE_END_PLACEHOLDER, "")
)
: source,
hasEndOfLine ? visualizedOutput : output,
Object.assign({}, baseOptions, { parsers }) Object.assign({}, baseOptions, { parsers })
) )
) )
).toMatchSnapshot(); ).toMatchSnapshot();
}); });
parsers.slice(1).forEach(parser => { for (const parser of parsers.slice(1)) {
const verifyOptions = Object.assign({}, mainOptions, { parser }); const verifyOptions = Object.assign({}, baseOptions, { parser });
test(`${filename} - ${parser}-verify`, () => { test(`${basename} - ${parser}-verify`, () => {
const verifyOutput = prettyprint(input, path, verifyOptions); const verifyOutput = format(input, filename, verifyOptions);
expect(output).toEqual(verifyOutput); expect(visualizedOutput).toEqual(visualizeEndOfLine(verifyOutput));
});
}); });
}
if (AST_COMPARE) { if (AST_COMPARE) {
test(`${path} parse`, () => { test(`${filename} parse`, () => {
const compareOptions = Object.assign({}, mainOptions); const parseOptions = Object.assign({}, mainOptions);
delete compareOptions.cursorOffset; delete parseOptions.cursorOffset;
const astMassaged = parse(input, compareOptions);
let ppastMassaged = undefined; const originalAst = parse(input, parseOptions);
let formattedAst;
expect(() => { expect(() => {
ppastMassaged = parse( formattedAst = parse(
prettyprint(input, path, compareOptions) output.replace(CURSOR_PLACEHOLDER, ""),
// \r has been replaced with /*CR*/ to test presence of CR in jest snapshots; parseOptions
// reverting this to get the right AST
.replace(/\/\*CR\*\//g, "\r"),
compareOptions
); );
}).not.toThrow(); }).not.toThrow();
expect(originalAst).toEqual(formattedAst);
expect(ppastMassaged).toBeDefined();
if (!astMassaged.errors || astMassaged.errors.length === 0) {
expect(astMassaged).toEqual(ppastMassaged);
}
}); });
} }
}
}); });
};
function parse(source, options) {
return prettier.__debug.parse(source, options, /* massage */ true).ast;
} }
global.run_spec = run_spec; function format(source, filename, options) {
function parse(string, opts) {
return prettier.__debug.parse(string, opts, /* massage */ true).ast;
}
function prettyprint(src, filename, options) {
const result = prettier.formatWithCursor( const result = prettier.formatWithCursor(
src, source,
Object.assign( Object.assign({ filepath: filename }, options)
{
filepath: filename
},
options
)
); );
if (options.cursorOffset >= 0) {
result.formatted = return options.cursorOffset >= 0
result.formatted.slice(0, result.cursorOffset) + ? result.formatted.slice(0, result.cursorOffset) +
"<|>" + CURSOR_PLACEHOLDER +
result.formatted.slice(result.cursorOffset); result.formatted.slice(result.cursorOffset)
: result.formatted;
}
function consistentEndOfLine(text) {
let firstEndOfLine;
return text.replace(/\r\n?|\n/g, endOfLine => {
if (!firstEndOfLine) {
firstEndOfLine = endOfLine;
} }
return firstEndOfLine;
// \r is trimmed from jest snapshots by default; });
// manually replacing this character with /*CR*/ to test its true presence
return result.formatted.replace(/\r/g, "/*CR*/");
} }
function read(filename) { function visualizeEndOfLine(text) {
return fs.readFileSync(filename, "utf8"); return text.replace(/\r\n?|\n/g, endOfLine => {
} switch (endOfLine) {
case "\n":
function skipStandalone(/* parser */) { return "<LF>\n";
return false; case "\r\n":
} return "<CRLF>\n";
case "\r":
function mergeDefaultOptions(parserConfig) { return "<CR>\n";
return Object.assign( default:
{ throw new Error(`Unexpected end of line ${JSON.stringify(endOfLine)}`);
printWidth: 80 }
}, });
parserConfig
);
} }
function createSnapshot(input, output, options) { function createSnapshot(input, output, options) {

View File

@ -1,62 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Has correct default Windows line endings (stderr) 1`] = `""`;
exports[`Has correct default Windows line endings (stdout) 1`] = `
"function f() {
console.log(\\"should have tab width 8\\")
}
function f() {
console.log(\\"should have space width 2\\")
}
function f() {
console.log(\\"should have space width 8\\")
}
function f() {
console.log(
\\"should have space width 2 despite ../.editorconfig specifying 8, because ./.hg is present\\"
)
}
console.log(\\"jest/__best-tests__/file.js should have semi\\");/*CR*/
console.log(\\"jest/Component.js should not have semi\\")/*CR*/
console.log(\\"jest/Component.test.js should have semi\\");/*CR*/
function js() {
console.log(\\"js/file.js should have tab width 8 (1 if CLI)\\");
}
\\"use strict\\";
module.exports = {
endOfLine: \\"lf\\",
tabWidth: 8
};
function noConfigJs() {
console.log(\\"no-config/file.js should have no semicolons\\")
}
function packageJs() {/*CR*/
console.log(\\"package/file.js should have tab width 3\\");/*CR*/
}/*CR*/
function rcJson() {/*CR*/
console.log.apply(null, [/*CR*/
'rc-json/file.js',/*CR*/
'should have trailing comma',/*CR*/
'and single quotes',/*CR*/
]);/*CR*/
}/*CR*/
function rcToml() {/*CR*/
console.log.apply(null, [/*CR*/
'rc-toml/file.js',/*CR*/
'should have trailing comma',/*CR*/
'and single quotes',/*CR*/
]);/*CR*/
}/*CR*/
function rcYaml() {/*CR*/
console.log.apply(null, [/*CR*/
'rc-yaml/file.js',/*CR*/
'should have trailing comma',/*CR*/
'and single quotes',/*CR*/
]);/*CR*/
}/*CR*/
"
`;
exports[`Has correct default Windows line endings (write) 1`] = `Array []`;

View File

@ -1,62 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Has correct default Unix line endings (stderr) 1`] = `""`;
exports[`Has correct default Unix line endings (stdout) 1`] = `
"function f() {
console.log(\\"should have tab width 8\\")
}
function f() {
console.log(\\"should have space width 2\\")
}
function f() {
console.log(\\"should have space width 8\\")
}
function f() {
console.log(
\\"should have space width 2 despite ../.editorconfig specifying 8, because ./.hg is present\\"
)
}
console.log(\\"jest/__best-tests__/file.js should have semi\\");
console.log(\\"jest/Component.js should not have semi\\")
console.log(\\"jest/Component.test.js should have semi\\");
function js() {
console.log(\\"js/file.js should have tab width 8 (1 if CLI)\\");
}
\\"use strict\\";
module.exports = {
endOfLine: \\"lf\\",
tabWidth: 8
};
function noConfigJs() {
console.log(\\"no-config/file.js should have no semicolons\\")
}
function packageJs() {
console.log(\\"package/file.js should have tab width 3\\");
}
function rcJson() {
console.log.apply(null, [
'rc-json/file.js',
'should have trailing comma',
'and single quotes',
]);
}
function rcToml() {
console.log.apply(null, [
'rc-toml/file.js',
'should have trailing comma',
'and single quotes',
]);
}
function rcYaml() {
console.log.apply(null, [
'rc-yaml/file.js',
'should have trailing comma',
'and single quotes',
]);
}
"
`;
exports[`Has correct default Unix line endings (write) 1`] = `Array []`;

View File

@ -1,11 +0,0 @@
"use strict";
const runPrettier = require("../runPrettier");
expect.addSnapshotSerializer(require("../path-serializer"));
describe("Has correct default Windows line endings", () => {
runPrettier("cli/config/", ["**/*.js"]).test({
status: 0
});
});

View File

@ -1,11 +0,0 @@
"use strict";
const runPrettier = require("../runPrettier");
expect.addSnapshotSerializer(require("../path-serializer"));
describe("Has correct default Unix line endings", () => {
runPrettier("cli/config/", ["**/*.js"]).test({
status: 0
});
});

View File

@ -6132,16 +6132,16 @@ yallist@^3.0.0, yallist@^3.0.2:
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9"
yaml-unist-parser@1.0.0-rc.4: yaml-unist-parser@1.0.0:
version "1.0.0-rc.4" version "1.0.0"
resolved "https://registry.yarnpkg.com/yaml-unist-parser/-/yaml-unist-parser-1.0.0-rc.4.tgz#d8fb9c673d59a4f7d532840b120abd6400237014" resolved "https://registry.yarnpkg.com/yaml-unist-parser/-/yaml-unist-parser-1.0.0.tgz#060def481d2319a8def3b6a06cb8ae3848b0aed3"
dependencies: dependencies:
lines-and-columns "^1.1.6" lines-and-columns "^1.1.6"
tslib "^1.9.1" tslib "^1.9.1"
yaml@1.0.0-rc.8: yaml@1.0.2:
version "1.0.0-rc.8" version "1.0.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.0.0-rc.8.tgz#e5604c52b7b07b16e469bcf875ab0dfe08c50d42" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.0.2.tgz#77941a457090e17b8ca65b53322e68a050e090d4"
yargs-parser@^7.0.0: yargs-parser@^7.0.0:
version "7.0.0" version "7.0.0"