refactor css comments (#7021)
* refactor css comments * workarounds for postcss-less location bugs * improve replaceQuotesInInlineComments * update changelog * fix comments in selectors * fix comments in values * use \ufffe instead of \0 because that's what these characters are for see http://www.unicode.org/faq/private_use.html#nonchar6 * fix quoted urls Co-authored-by: Georgii Dolzhykov <thorn.mailbox@gmail.com>
This commit is contained in:
1 parent
1ad13d7420
commit
4602bb6b95
11 files changed
+455
-38
No files matched your search
@@ -0,0 +1,178 @@
|
||||
"use strict";
|
||||
|
||||
const lineColumnToIndex = require("../utils/line-column-to-index");
|
||||
const { getLast, skipEverythingButNewLine } = require("../common/util");
|
||||
|
||||
function calculateLocStart(node, text) {
|
||||
if (node.source) {
|
||||
return lineColumnToIndex(node.source.start, text) - 1;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function calculateLocEnd(node, text) {
|
||||
if (node.type === "css-comment" && node.inline) {
|
||||
return skipEverythingButNewLine(text, node.source.startOffset);
|
||||
}
|
||||
const endNode = node.nodes && getLast(node.nodes);
|
||||
if (endNode && node.source && !node.source.end) {
|
||||
node = endNode;
|
||||
}
|
||||
if (node.source && node.source.end) {
|
||||
return lineColumnToIndex(node.source.end, text);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function calculateLoc(node, text) {
|
||||
if (node && typeof node === "object") {
|
||||
if (node.source) {
|
||||
node.source.startOffset = calculateLocStart(node, text);
|
||||
node.source.endOffset = calculateLocEnd(node, text);
|
||||
}
|
||||
|
||||
for (const key in node) {
|
||||
calculateLoc(node[key], text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Workaround for a bug: quotes in inline comments corrupt loc data of subsequent nodes.
|
||||
* This function replaces the quotes with U+FFFE and U+FFFF. Later, when the comments are printed,
|
||||
* their content is extracted from the original text or restored by replacing the placeholder
|
||||
* characters back with quotes.
|
||||
* - https://github.com/prettier/prettier/issues/7780
|
||||
* - https://github.com/shellscape/postcss-less/issues/145
|
||||
* - About noncharacters (U+FFFE and U+FFFF): http://www.unicode.org/faq/private_use.html#nonchar1
|
||||
* @param text {string}
|
||||
*/
|
||||
function replaceQuotesInInlineComments(text) {
|
||||
/** @typedef { 'initial' | 'single-quotes' | 'double-quotes' | 'url' | 'comment-block' | 'comment-inline' } State */
|
||||
/** @type {State} */
|
||||
let state = "initial";
|
||||
/** @type {State} */
|
||||
let stateToReturnFromQuotes = "initial";
|
||||
let inlineCommentStartIndex;
|
||||
let inlineCommentContainsQuotes = false;
|
||||
const inlineCommentsToReplace = [];
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const c = text[i];
|
||||
|
||||
switch (state) {
|
||||
case "initial":
|
||||
if (c === "'") {
|
||||
state = "single-quotes";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c === '"') {
|
||||
state = "double-quotes";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
(c === "u" || c === "U") &&
|
||||
text.slice(i, i + 4).toLowerCase() === "url("
|
||||
) {
|
||||
state = "url";
|
||||
i += 3;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c === "*" && text[i - 1] === "/") {
|
||||
state = "comment-block";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c === "/" && text[i - 1] === "/") {
|
||||
state = "comment-inline";
|
||||
inlineCommentStartIndex = i - 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
continue;
|
||||
|
||||
case "single-quotes":
|
||||
if (c === "'" && text[i - 1] !== "\\") {
|
||||
state = stateToReturnFromQuotes;
|
||||
stateToReturnFromQuotes = "initial";
|
||||
}
|
||||
if (c === "\n" || c === "\r") {
|
||||
return text; // invalid input
|
||||
}
|
||||
continue;
|
||||
|
||||
case "double-quotes":
|
||||
if (c === '"' && text[i - 1] !== "\\") {
|
||||
state = stateToReturnFromQuotes;
|
||||
stateToReturnFromQuotes = "initial";
|
||||
}
|
||||
if (c === "\n" || c === "\r") {
|
||||
return text; // invalid input
|
||||
}
|
||||
continue;
|
||||
|
||||
case "url":
|
||||
if (c === ")") {
|
||||
state = "initial";
|
||||
}
|
||||
if (c === "\n" || c === "\r") {
|
||||
return text; // invalid input
|
||||
}
|
||||
if (c === "'") {
|
||||
state = "single-quotes";
|
||||
stateToReturnFromQuotes = "url";
|
||||
continue;
|
||||
}
|
||||
if (c === '"') {
|
||||
state = "double-quotes";
|
||||
stateToReturnFromQuotes = "url";
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
|
||||
case "comment-block":
|
||||
if (c === "/" && text[i - 1] === "*") {
|
||||
state = "initial";
|
||||
}
|
||||
continue;
|
||||
|
||||
case "comment-inline":
|
||||
if (c === '"' || c === "'") {
|
||||
inlineCommentContainsQuotes = true;
|
||||
}
|
||||
if (c === "\n" || c === "\r") {
|
||||
if (inlineCommentContainsQuotes) {
|
||||
inlineCommentsToReplace.push([inlineCommentStartIndex, i]);
|
||||
}
|
||||
state = "initial";
|
||||
inlineCommentContainsQuotes = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (const [start, end] of inlineCommentsToReplace) {
|
||||
text =
|
||||
text.slice(0, start) +
|
||||
text
|
||||
.slice(start, end)
|
||||
.replace(/'/g, "\ufffe")
|
||||
.replace(/"/g, "\uffff") +
|
||||
text.slice(end);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
function restoreQuotesInInlineComments(text) {
|
||||
return text.replace(/\ufffe/g, "'").replace(/\uffff/g, '"');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
calculateLoc,
|
||||
replaceQuotesInInlineComments,
|
||||
restoreQuotesInInlineComments
|
||||
};
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
const createError = require("../common/parser-create-error");
|
||||
const parseFrontMatter = require("../utils/front-matter");
|
||||
const lineColumnToIndex = require("../utils/line-column-to-index");
|
||||
const { hasPragma } = require("./pragma");
|
||||
const { isLessParser, isSCSS, isSCSSNestedPropertyNode } = require("./utils");
|
||||
const { calculateLoc, replaceQuotesInInlineComments } = require("./loc");
|
||||
|
||||
function parseValueNodes(nodes) {
|
||||
let parenGroup = {
|
||||
@@ -294,7 +294,7 @@ function parseNestedCSS(node, options) {
|
||||
|
||||
value = value.trim();
|
||||
|
||||
node.raws.value = selector;
|
||||
node.raws.value = value;
|
||||
}
|
||||
|
||||
let params = "";
|
||||
@@ -541,6 +541,8 @@ function parseWithParser(parser, text, options) {
|
||||
|
||||
result = parseNestedCSS(addTypePrefix(result, "css-"), options);
|
||||
|
||||
calculateLoc(result, text);
|
||||
|
||||
if (frontMatter) {
|
||||
result.nodes.unshift(frontMatter);
|
||||
}
|
||||
@@ -553,7 +555,12 @@ function requireParser(isSCSSParser) {
|
||||
return require("postcss-scss");
|
||||
}
|
||||
|
||||
return require("postcss-less");
|
||||
const lessParser = require("postcss-less");
|
||||
return {
|
||||
// Workaround for https://github.com/shellscape/postcss-less/issues/145
|
||||
// See comments for `replaceQuotesInInlineComments` in `loc.js`.
|
||||
parse: text => lessParser.parse(replaceQuotesInInlineComments(text))
|
||||
};
|
||||
}
|
||||
|
||||
function parse(text, parsers, options) {
|
||||
@@ -582,17 +589,13 @@ const parser = {
|
||||
hasPragma,
|
||||
locStart(node) {
|
||||
if (node.source) {
|
||||
return lineColumnToIndex(node.source.start, node.source.input.css) - 1;
|
||||
return node.source.startOffset;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
locEnd(node) {
|
||||
const endNode = node.nodes && node.nodes[node.nodes.length - 1];
|
||||
if (endNode && node.source && !node.source.end) {
|
||||
node = endNode;
|
||||
}
|
||||
if (node.source && node.source.end) {
|
||||
return lineColumnToIndex(node.source.end, node.source.input.css);
|
||||
if (node.source) {
|
||||
return node.source.endOffset;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ const {
|
||||
hasNewline
|
||||
} = require("../common/util");
|
||||
const { isNextLineEmpty } = require("../common/util-shared");
|
||||
const { restoreQuotesInInlineComments } = require("./loc");
|
||||
|
||||
const {
|
||||
builders: {
|
||||
@@ -110,31 +111,13 @@ function genericPrint(path, options, print) {
|
||||
return nodes;
|
||||
}
|
||||
case "css-comment": {
|
||||
if (node.raws.content) {
|
||||
return node.raws.content;
|
||||
}
|
||||
const isInlineComment = node.inline || node.raws.inline;
|
||||
const text = options.originalText.slice(
|
||||
options.locStart(node),
|
||||
options.locEnd(node)
|
||||
);
|
||||
|
||||
const rawText = node.raws.text || node.text;
|
||||
// Workaround a bug where the location is off.
|
||||
// https://github.com/postcss/postcss-scss/issues/63
|
||||
if (!text.includes(rawText)) {
|
||||
if (node.raws.inline || node.inline) {
|
||||
const needBreakAfter = !(
|
||||
node.source.input.css.split(/[\r\n]/)[node.source.end.line] || ""
|
||||
).trim();
|
||||
return concat([
|
||||
"//",
|
||||
node.raws.left + rawText,
|
||||
needBreakAfter ? line : ""
|
||||
]);
|
||||
}
|
||||
return concat(["/* ", rawText, " */"]);
|
||||
}
|
||||
return text;
|
||||
return isInlineComment ? text.trimEnd() : text;
|
||||
}
|
||||
case "css-rule": {
|
||||
return concat([
|
||||
@@ -481,6 +464,14 @@ function genericPrint(path, options, print) {
|
||||
);
|
||||
}
|
||||
|
||||
// originalText has to be used for Less, see replaceQuotesInInlineComments in loc.js
|
||||
const parentNode = path.getParentNode();
|
||||
if (parentNode.raws && parentNode.raws.selector) {
|
||||
const start = options.locStart(parentNode);
|
||||
const end = start + parentNode.raws.selector.length;
|
||||
return options.originalText.slice(start, end).trim();
|
||||
}
|
||||
|
||||
return node.value;
|
||||
}
|
||||
// postcss-values-parser
|
||||
@@ -491,7 +482,9 @@ function genericPrint(path, options, print) {
|
||||
case "value-comment": {
|
||||
return concat([
|
||||
node.inline ? "//" : "/*",
|
||||
node.value,
|
||||
// see replaceQuotesInInlineComments in loc.js
|
||||
// value-* nodes don't have correct location data, so we have to rely on placeholder characters.
|
||||
restoreQuotesInInlineComments(node.value),
|
||||
node.inline ? "" : "*/"
|
||||
]);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user