Files
test-repo/src/language-html/print-preprocess.js
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

462 lines
13 KiB
JavaScript
Raw Normal View History

"use strict";
const {
ParseSourceSpan,
} = require("angular-html-parser/lib/compiler/src/parse_util");
const getLast = require("../utils/get-last");
const {
htmlTrim,
getLeadingAndTrailingHtmlWhitespace,
hasHtmlWhitespace,
2018-11-04 23:03:07 +08:00
canHaveInterpolation,
getNodeCssStyleDisplay,
isDanglingSpaceSensitiveNode,
2018-11-04 23:03:07 +08:00
isIndentationSensitiveNode,
isLeadingSpaceSensitiveNode,
isTrailingSpaceSensitiveNode,
2018-11-04 23:03:07 +08:00
isWhitespaceSensitiveNode,
} = require("./utils");
const PREPROCESS_PIPELINE = [
2018-11-04 23:03:07 +08:00
removeIgnorableFirstLf,
mergeIeConditonalStartEndCommentIntoElementOpeningTag,
2018-11-04 23:03:07 +08:00
mergeCdataIntoText,
extractInterpolation,
extractWhitespaces,
addCssDisplay,
2018-11-04 23:03:07 +08:00
addIsSelfClosing,
addHasHtmComponentClosingTag,
addIsSpaceSensitive,
2018-11-04 23:03:07 +08:00
mergeSimpleElementIntoText,
];
function preprocess(ast, options) {
for (const fn of PREPROCESS_PIPELINE) {
ast = fn(ast, options);
}
return ast;
}
2018-11-04 23:03:07 +08:00
function removeIgnorableFirstLf(ast /*, options */) {
return ast.map((node) => {
if (
2018-11-04 23:03:07 +08:00
node.type === "element" &&
node.tagDefinition.ignoreFirstLf &&
node.children.length > 0 &&
2018-11-04 23:03:07 +08:00
node.children[0].type === "text" &&
node.children[0].value[0] === "\n"
) {
const [text, ...rest] = node.children;
2018-11-04 23:03:07 +08:00
return node.clone({
children:
text.value.length === 1
? rest
: [text.clone({ value: text.value.slice(1) }), ...rest],
2018-11-04 23:03:07 +08:00
});
}
return node;
});
}
function mergeIeConditonalStartEndCommentIntoElementOpeningTag(
ast /*, options */
) {
/**
* <!--[if ...]><!--><target><!--<![endif]-->
*/
const isTarget = (node) =>
node.type === "element" &&
node.prev &&
node.prev.type === "ieConditionalStartComment" &&
node.prev.sourceSpan.end.offset === node.startSourceSpan.start.offset &&
node.firstChild &&
node.firstChild.type === "ieConditionalEndComment" &&
node.firstChild.sourceSpan.start.offset === node.startSourceSpan.end.offset;
return ast.map((node) => {
if (node.children) {
const isTargetResults = node.children.map(isTarget);
if (isTargetResults.some(Boolean)) {
const newChildren = [];
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i];
if (isTargetResults[i + 1]) {
// ieConditionalStartComment
continue;
}
if (isTargetResults[i]) {
const ieConditionalStartComment = child.prev;
const ieConditionalEndComment = child.firstChild;
const startSourceSpan = new ParseSourceSpan(
ieConditionalStartComment.sourceSpan.start,
ieConditionalEndComment.sourceSpan.end
);
const sourceSpan = new ParseSourceSpan(
startSourceSpan.start,
child.sourceSpan.end
);
newChildren.push(
child.clone({
condition: ieConditionalStartComment.condition,
sourceSpan,
startSourceSpan,
children: child.children.slice(1),
})
);
continue;
}
newChildren.push(child);
}
return node.clone({ children: newChildren });
}
}
return node;
});
}
2018-11-04 23:03:07 +08:00
function mergeNodeIntoText(ast, shouldMerge, getValue) {
return ast.map((node) => {
if (node.children) {
const shouldMergeResults = node.children.map(shouldMerge);
if (shouldMergeResults.some(Boolean)) {
const newChildren = [];
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i];
if (child.type !== "text" && !shouldMergeResults[i]) {
newChildren.push(child);
continue;
}
const newChild =
child.type === "text"
? child
: child.clone({ type: "text", value: getValue(child) });
if (
newChildren.length === 0 ||
getLast(newChildren).type !== "text"
2018-11-04 23:03:07 +08:00
) {
newChildren.push(newChild);
continue;
}
const lastChild = newChildren.pop();
newChildren.push(
lastChild.clone({
value: lastChild.value + newChild.value,
sourceSpan: new ParseSourceSpan(
lastChild.sourceSpan.start,
newChild.sourceSpan.end
),
})
);
}
return node.clone({ children: newChildren });
}
}
return node;
});
}
function mergeCdataIntoText(ast /*, options */) {
return mergeNodeIntoText(
ast,
(node) => node.type === "cdata",
(node) => `<![CDATA[${node.value}]]>`
);
}
function mergeSimpleElementIntoText(ast /*, options */) {
const isSimpleElement = (node) =>
node.type === "element" &&
node.attrs.length === 0 &&
node.children.length === 1 &&
node.firstChild.type === "text" &&
!hasHtmlWhitespace(node.children[0].value) &&
2018-11-04 23:03:07 +08:00
!node.firstChild.hasLeadingSpaces &&
!node.firstChild.hasTrailingSpaces &&
node.isLeadingSpaceSensitive &&
!node.hasLeadingSpaces &&
node.isTrailingSpaceSensitive &&
!node.hasTrailingSpaces &&
node.prev &&
node.prev.type === "text" &&
node.next &&
node.next.type === "text";
return ast.map((node) => {
if (node.children) {
const isSimpleElementResults = node.children.map(isSimpleElement);
if (isSimpleElementResults.some(Boolean)) {
const newChildren = [];
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i];
if (isSimpleElementResults[i]) {
const lastChild = newChildren.pop();
const nextChild = node.children[++i];
const { isTrailingSpaceSensitive, hasTrailingSpaces } = nextChild;
newChildren.push(
lastChild.clone({
value:
lastChild.value +
`<${child.rawName}>` +
child.firstChild.value +
`</${child.rawName}>` +
nextChild.value,
sourceSpan: new ParseSourceSpan(
lastChild.sourceSpan.start,
nextChild.sourceSpan.end
),
isTrailingSpaceSensitive,
hasTrailingSpaces,
})
);
} else {
newChildren.push(child);
}
}
return node.clone({ children: newChildren });
}
}
return node;
});
}
function extractInterpolation(ast, options) {
if (options.parser === "html") {
return ast;
}
2021-05-18 02:44:21 +08:00
const interpolationRegex = /{{(.+?)}}/gs;
2018-11-04 23:03:07 +08:00
return ast.map((node) => {
if (!canHaveInterpolation(node)) {
return node;
}
2018-11-04 23:03:07 +08:00
const newChildren = [];
2018-11-04 23:03:07 +08:00
for (const child of node.children) {
if (child.type !== "text") {
newChildren.push(child);
continue;
}
let startSourceSpan = child.sourceSpan.start;
let endSourceSpan = null;
const components = child.value.split(interpolationRegex);
for (
let i = 0;
i < components.length;
i++, startSourceSpan = endSourceSpan
) {
const value = components[i];
if (i % 2 === 0) {
endSourceSpan = startSourceSpan.moveBy(value.length);
if (value.length > 0) {
2018-11-04 23:03:07 +08:00
newChildren.push({
type: "text",
value,
sourceSpan: new ParseSourceSpan(startSourceSpan, endSourceSpan),
});
}
continue;
}
endSourceSpan = startSourceSpan.moveBy(value.length + 4); // `{{` + `}}`
newChildren.push({
type: "interpolation",
sourceSpan: new ParseSourceSpan(startSourceSpan, endSourceSpan),
children:
value.length === 0
? []
: [
{
type: "text",
value,
sourceSpan: new ParseSourceSpan(
startSourceSpan.moveBy(2),
endSourceSpan.moveBy(-2)
),
},
],
});
}
}
return node.clone({ children: newChildren });
});
}
/**
* - add `hasLeadingSpaces` field
* - add `hasTrailingSpaces` field
* - add `hasDanglingSpaces` field for parent nodes
2018-11-04 23:03:07 +08:00
* - add `isWhitespaceSensitive`, `isIndentationSensitive` field for text nodes
* - remove insensitive whitespaces
*/
2021-05-16 12:43:13 +08:00
const WHITESPACE_NODE = { type: "whitespace" };
function extractWhitespaces(ast /*, options*/) {
2018-11-04 23:03:07 +08:00
return ast.map((node) => {
if (!node.children) {
return node;
}
if (
node.children.length === 0 ||
(node.children.length === 1 &&
node.children[0].type === "text" &&
htmlTrim(node.children[0].value).length === 0)
) {
2018-11-04 23:03:07 +08:00
return node.clone({
children: [],
hasDanglingSpaces: node.children.length > 0,
});
}
2018-11-04 23:03:07 +08:00
const isWhitespaceSensitive = isWhitespaceSensitiveNode(node);
const isIndentationSensitive = isIndentationSensitiveNode(node);
2018-11-04 23:03:07 +08:00
return node.clone({
isWhitespaceSensitive,
isIndentationSensitive,
children: node.children
// extract whitespace nodes
2021-05-16 12:43:13 +08:00
.flatMap((child) => {
if (child.type !== "text" || isWhitespaceSensitive) {
2021-05-16 12:43:13 +08:00
return child;
}
const localChildren = [];
const { leadingWhitespace, text, trailingWhitespace } =
getLeadingAndTrailingHtmlWhitespace(child.value);
if (leadingWhitespace) {
2021-05-16 12:43:13 +08:00
localChildren.push(WHITESPACE_NODE);
}
if (text) {
localChildren.push({
type: "text",
2018-11-04 23:03:07 +08:00
value: text,
sourceSpan: new ParseSourceSpan(
child.sourceSpan.start.moveBy(leadingWhitespace.length),
child.sourceSpan.end.moveBy(-trailingWhitespace.length)
2018-11-04 23:03:07 +08:00
),
});
}
if (trailingWhitespace) {
2021-05-16 12:43:13 +08:00
localChildren.push(WHITESPACE_NODE);
}
2021-05-16 12:43:13 +08:00
return localChildren;
})
// set hasLeadingSpaces/hasTrailingSpaces
.map((child, index, children) => {
if (child === WHITESPACE_NODE) {
return;
}
2021-05-16 12:43:13 +08:00
return {
...child,
hasLeadingSpaces: children[index - 1] === WHITESPACE_NODE,
hasTrailingSpaces: children[index + 1] === WHITESPACE_NODE,
};
})
// filter whitespace nodes
.filter(Boolean),
});
});
}
2018-11-04 23:03:07 +08:00
function addIsSelfClosing(ast /*, options */) {
return ast.map((node) =>
Object.assign(node, {
isSelfClosing:
!node.children ||
(node.type === "element" &&
(node.tagDefinition.isVoid ||
// self-closing
node.startSourceSpan === node.endSourceSpan)),
})
);
}
function addHasHtmComponentClosingTag(ast, options) {
return ast.map((node) =>
node.type !== "element"
? node
: Object.assign(node, {
hasHtmComponentClosingTag:
node.endSourceSpan &&
/^<\s*\/\s*\/\s*>$/.test(
options.originalText.slice(
node.endSourceSpan.start.offset,
node.endSourceSpan.end.offset
)
),
})
);
}
function addCssDisplay(ast, options) {
2018-11-04 23:03:07 +08:00
return ast.map((node) =>
Object.assign(node, { cssDisplay: getNodeCssStyleDisplay(node, options) })
);
}
/**
* - add `isLeadingSpaceSensitive` field
* - add `isTrailingSpaceSensitive` field
* - add `isDanglingSpaceSensitive` field for parent nodes
*/
2020-04-17 07:54:24 +09:00
function addIsSpaceSensitive(ast, options) {
2018-11-04 23:03:07 +08:00
return ast.map((node) => {
if (!node.children) {
return node;
}
if (node.children.length === 0) {
2018-11-04 23:03:07 +08:00
return node.clone({
isDanglingSpaceSensitive: isDanglingSpaceSensitiveNode(node),
});
}
2018-11-04 23:03:07 +08:00
return node.clone({
children: node.children
2020-12-17 14:31:51 +08:00
.map((child) => ({
...child,
isLeadingSpaceSensitive: isLeadingSpaceSensitiveNode(child, options),
isTrailingSpaceSensitive: isTrailingSpaceSensitiveNode(
child,
options
),
}))
.map((child, index, children) => ({
...child,
isLeadingSpaceSensitive:
index === 0
? child.isLeadingSpaceSensitive
: children[index - 1].isTrailingSpaceSensitive &&
child.isLeadingSpaceSensitive,
isTrailingSpaceSensitive:
index === children.length - 1
? child.isTrailingSpaceSensitive
: children[index + 1].isLeadingSpaceSensitive &&
child.isTrailingSpaceSensitive,
})),
});
});
}
module.exports = preprocess;