feat: support HTML/Vue/Angular (#5259)
parent
0878a6a3e7
commit
5e8a4a115a
|
@ -14,10 +14,13 @@
|
|||
"node": ">=6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/compiler": "6.1.10",
|
||||
"@babel/code-frame": "7.0.0-beta.46",
|
||||
"@babel/parser": "7.1.2",
|
||||
"@glimmer/syntax": "0.30.3",
|
||||
"@iarna/toml": "2.0.0",
|
||||
"angular-estree-parser": "1.1.3",
|
||||
"angular-html-parser": "1.0.0",
|
||||
"camelcase": "4.1.0",
|
||||
"chalk": "2.1.0",
|
||||
"cjk-regex": "2.0.0",
|
||||
|
@ -25,7 +28,6 @@
|
|||
"dashify": "0.2.2",
|
||||
"dedent": "0.7.0",
|
||||
"diff": "3.2.0",
|
||||
"domhandler": "2.4.2",
|
||||
"editorconfig": "0.15.2",
|
||||
"editorconfig-to-prettier": "0.0.6",
|
||||
"emoji-regex": "6.5.1",
|
||||
|
@ -40,7 +42,6 @@
|
|||
"html-element-attributes": "2.0.0",
|
||||
"html-styles": "1.0.0",
|
||||
"html-tag-names": "1.1.2",
|
||||
"htmlparser2": "3.9.2",
|
||||
"ignore": "3.3.7",
|
||||
"jest-docblock": "23.2.0",
|
||||
"json-stable-stringify": "1.0.1",
|
||||
|
@ -53,7 +54,7 @@
|
|||
"minimist": "1.2.0",
|
||||
"n-readlines": "1.0.0",
|
||||
"normalize-path": "3.0.0",
|
||||
"parse5-htmlparser2-tree-adapter": "5.0.0",
|
||||
"parse-srcset": "ikatyang/parse-srcset#feat/report-error",
|
||||
"postcss-less": "1.1.5",
|
||||
"postcss-media-query-parser": "0.2.3",
|
||||
"postcss-scss": "1.0.6",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
const path = require("path");
|
||||
const PROJECT_ROOT = path.resolve(__dirname, "../..");
|
||||
|
||||
/**
|
||||
* @typedef {Object} Bundle
|
||||
|
@ -38,6 +39,18 @@ const parsers = [
|
|||
input: "src/language-js/parser-typescript.js",
|
||||
target: "universal"
|
||||
},
|
||||
{
|
||||
input: "src/language-js/parser-angular.js",
|
||||
target: "universal",
|
||||
alias: {
|
||||
// Force using the CJS file, instead of ESM; i.e. get the file
|
||||
// from `"main"` instead of `"module"` (rollup default) of package.json
|
||||
"lines-and-columns": require.resolve("lines-and-columns"),
|
||||
"@angular/compiler/src": path.resolve(
|
||||
`${PROJECT_ROOT}/node_modules/@angular/compiler/esm2015/src`
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
input: "src/language-css/parser-postcss.js",
|
||||
target: "universal",
|
||||
|
@ -52,10 +65,6 @@ const parsers = [
|
|||
input: "src/language-markdown/parser-markdown.js",
|
||||
target: "universal"
|
||||
},
|
||||
{
|
||||
input: "src/language-vue/parser-vue.js",
|
||||
target: "universal"
|
||||
},
|
||||
{
|
||||
input: "src/language-handlebars/parser-glimmer.js",
|
||||
target: "universal",
|
||||
|
|
|
@ -29,6 +29,10 @@ module.exports = [
|
|||
return eval("require")("../language-js/parser-babylon").parsers
|
||||
.__js_expression;
|
||||
},
|
||||
get __vue_expression() {
|
||||
return eval("require")("../language-js/parser-babylon").parsers
|
||||
.__vue_expression;
|
||||
},
|
||||
// JS - Flow
|
||||
get flow() {
|
||||
return eval("require")("../language-js/parser-flow").parsers.flow;
|
||||
|
@ -44,6 +48,26 @@ module.exports = [
|
|||
get "typescript-eslint"() {
|
||||
return eval("require")("../language-js/parser-typescript").parsers
|
||||
.typescript;
|
||||
},
|
||||
// JS - Angular Action
|
||||
get __ng_action() {
|
||||
return eval("require")("../language-js/parser-angular").parsers
|
||||
.__ng_action;
|
||||
},
|
||||
// JS - Angular Binding
|
||||
get __ng_binding() {
|
||||
return eval("require")("../language-js/parser-angular").parsers
|
||||
.__ng_binding;
|
||||
},
|
||||
// JS - Angular Interpolation
|
||||
get __ng_interpolation() {
|
||||
return eval("require")("../language-js/parser-angular").parsers
|
||||
.__ng_interpolation;
|
||||
},
|
||||
// JS - Angular Directive
|
||||
get __ng_directive() {
|
||||
return eval("require")("../language-js/parser-angular").parsers
|
||||
.__ng_directive;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -107,22 +131,20 @@ module.exports = [
|
|||
}
|
||||
},
|
||||
|
||||
// HTML
|
||||
require("../language-html"),
|
||||
{
|
||||
parsers: {
|
||||
// HTML
|
||||
get html() {
|
||||
return eval("require")("../language-html/parser-html").parsers.html;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Vue
|
||||
require("../language-vue"),
|
||||
{
|
||||
parsers: {
|
||||
get vue() {
|
||||
return eval("require")("../language-vue/parser-vue").parsers.vue;
|
||||
return eval("require")("../language-html/parser-html").parsers.vue;
|
||||
},
|
||||
// Angular
|
||||
get angular() {
|
||||
return eval("require")("../language-html/parser-html").parsers.angular;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -464,6 +464,8 @@ function printString(raw, options, isDirectiveLiteral) {
|
|||
const enclosingQuote =
|
||||
options.parser === "json"
|
||||
? double.quote
|
||||
: options.__isInHtmlAttribute
|
||||
? single.quote
|
||||
: shouldUseAlternateQuote
|
||||
? alternate.quote
|
||||
: preferred.quote;
|
||||
|
@ -489,7 +491,10 @@ function printString(raw, options, isDirectiveLiteral) {
|
|||
!(
|
||||
options.parser === "css" ||
|
||||
options.parser === "less" ||
|
||||
options.parser === "scss"
|
||||
options.parser === "scss" ||
|
||||
options.parentParser === "html" ||
|
||||
options.parentParser === "vue" ||
|
||||
options.parentParser === "angular"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
"use strict";
|
||||
|
||||
const NODES_KEYS = {
|
||||
attrs: true,
|
||||
children: true
|
||||
};
|
||||
|
||||
class Node {
|
||||
constructor(props = {}) {
|
||||
for (const key of Object.keys(props)) {
|
||||
const value = props[key];
|
||||
if (key in NODES_KEYS) {
|
||||
this._setNodes(key, value);
|
||||
} else {
|
||||
this[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_setNodes(key, nodes) {
|
||||
if (nodes !== this[key]) {
|
||||
this[key] = cloneAndUpdateNodes(nodes, this);
|
||||
if (key === "attrs") {
|
||||
setNonEnumerableProperties(this, {
|
||||
attrMap: this[key].reduce((reduced, attr) => {
|
||||
reduced[attr.fullName] = attr.value;
|
||||
return reduced;
|
||||
}, Object.create(null))
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
map(fn) {
|
||||
let newNode = null;
|
||||
|
||||
for (const NODES_KEY in NODES_KEYS) {
|
||||
const nodes = this[NODES_KEY];
|
||||
if (nodes) {
|
||||
const mappedNodes = mapNodesIfChanged(nodes, node => node.map(fn));
|
||||
if (newNode !== nodes) {
|
||||
if (!newNode) {
|
||||
newNode = new Node();
|
||||
}
|
||||
newNode._setNodes(NODES_KEY, mappedNodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newNode) {
|
||||
for (const key in this) {
|
||||
if (!(key in NODES_KEYS)) {
|
||||
newNode[key] = this[key];
|
||||
}
|
||||
}
|
||||
const { index, siblings, prev, next, parent } = this;
|
||||
setNonEnumerableProperties(newNode, {
|
||||
index,
|
||||
siblings,
|
||||
prev,
|
||||
next,
|
||||
parent
|
||||
});
|
||||
}
|
||||
|
||||
return fn(newNode || this);
|
||||
}
|
||||
|
||||
clone(overrides) {
|
||||
return new Node(overrides ? Object.assign({}, this, overrides) : this);
|
||||
}
|
||||
|
||||
get firstChild() {
|
||||
return this.children && this.children.length !== 0
|
||||
? this.children[0]
|
||||
: null;
|
||||
}
|
||||
|
||||
get lastChild() {
|
||||
return this.children && this.children.length !== 0
|
||||
? this.children[this.children.length - 1]
|
||||
: null;
|
||||
}
|
||||
|
||||
// for element and attribute
|
||||
get rawName() {
|
||||
return this.hasExplicitNamespace ? this.fullName : this.name;
|
||||
}
|
||||
get fullName() {
|
||||
return this.namespace ? this.namespace + ":" + this.name : this.name;
|
||||
}
|
||||
}
|
||||
|
||||
function mapNodesIfChanged(nodes, fn) {
|
||||
const newNodes = nodes.map(fn);
|
||||
return newNodes.some((newNode, index) => newNode !== nodes[index])
|
||||
? newNodes
|
||||
: nodes;
|
||||
}
|
||||
|
||||
function cloneAndUpdateNodes(nodes, parent) {
|
||||
const siblings = nodes.map(
|
||||
node => (node instanceof Node ? node.clone() : new Node(node))
|
||||
);
|
||||
|
||||
let prev = null;
|
||||
let current = siblings[0];
|
||||
let next = siblings[1] || null;
|
||||
|
||||
for (let index = 0; index < siblings.length; index++) {
|
||||
setNonEnumerableProperties(current, {
|
||||
index,
|
||||
siblings,
|
||||
prev,
|
||||
next,
|
||||
parent
|
||||
});
|
||||
prev = current;
|
||||
current = next;
|
||||
next = siblings[index + 2] || null;
|
||||
}
|
||||
|
||||
return siblings;
|
||||
}
|
||||
|
||||
function setNonEnumerableProperties(obj, props) {
|
||||
const descriptors = Object.keys(props).reduce((reduced, key) => {
|
||||
reduced[key] = { value: props[key], enumerable: false };
|
||||
return reduced;
|
||||
}, {});
|
||||
Object.defineProperties(obj, descriptors);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Node
|
||||
};
|
|
@ -1,9 +1,11 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = function(ast, newNode) {
|
||||
delete newNode.startIndex;
|
||||
delete newNode.endIndex;
|
||||
delete newNode.attribs;
|
||||
delete newNode.sourceSpan;
|
||||
delete newNode.startSourceSpan;
|
||||
delete newNode.endSourceSpan;
|
||||
delete newNode.nameSpan;
|
||||
delete newNode.valueSpan;
|
||||
|
||||
if (ast.type === "text" || ast.type === "comment") {
|
||||
return null;
|
||||
|
@ -18,7 +20,7 @@ module.exports = function(ast, newNode) {
|
|||
delete newNode.value;
|
||||
}
|
||||
|
||||
if (ast.type === "directive" && ast.name === "!doctype") {
|
||||
delete newNode.data;
|
||||
if (ast.type === "docType") {
|
||||
delete newNode.value;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -17,7 +17,19 @@ const getCssStyleTags = property =>
|
|||
)
|
||||
.reduce((reduced, value) => Object.assign(reduced, value), {});
|
||||
|
||||
const CSS_DISPLAY_TAGS = getCssStyleTags("display");
|
||||
const CSS_DISPLAY_TAGS = Object.assign({}, getCssStyleTags("display"), {
|
||||
// TODO: send PR to upstream
|
||||
button: "inline-block",
|
||||
|
||||
// special cases for some css display=none elements
|
||||
template: "inline",
|
||||
source: "block",
|
||||
track: "block",
|
||||
|
||||
// there's no css display for these elements but they behave these ways
|
||||
video: "inline-block",
|
||||
audio: "inline-block"
|
||||
});
|
||||
const CSS_DISPLAY_DEFAULT = "inline";
|
||||
const CSS_WHITE_SPACE_TAGS = getCssStyleTags("white-space");
|
||||
const CSS_WHITE_SPACE_DEFAULT = "normal";
|
||||
|
|
|
@ -1,21 +1,39 @@
|
|||
"use strict";
|
||||
|
||||
const printer = require("./printer-htmlparser2");
|
||||
const printer = require("./printer-html");
|
||||
const createLanguage = require("../utils/create-language");
|
||||
const options = require("./options");
|
||||
|
||||
const languages = [
|
||||
createLanguage(require("linguist-languages/data/html"), {
|
||||
override: {
|
||||
name: "Angular",
|
||||
since: "1.15.0",
|
||||
parsers: ["angular"],
|
||||
vscodeLanguageIds: ["html"],
|
||||
|
||||
extensions: [".component.html"],
|
||||
filenames: []
|
||||
}
|
||||
}),
|
||||
createLanguage(require("linguist-languages/data/html"), {
|
||||
override: {
|
||||
since: "1.15.0",
|
||||
parsers: ["html"],
|
||||
vscodeLanguageIds: ["html"]
|
||||
}
|
||||
}),
|
||||
createLanguage(require("linguist-languages/data/vue"), {
|
||||
override: {
|
||||
since: "1.10.0",
|
||||
parsers: ["vue"],
|
||||
vscodeLanguageIds: ["vue"]
|
||||
}
|
||||
})
|
||||
];
|
||||
|
||||
const printers = {
|
||||
htmlparser2: printer
|
||||
html: printer
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
|
|
@ -1,225 +1,299 @@
|
|||
"use strict";
|
||||
|
||||
const parseFrontMatter = require("../utils/front-matter");
|
||||
const { HTML_ELEMENT_ATTRIBUTES, HTML_TAGS, mapNode } = require("./utils");
|
||||
const { HTML_ELEMENT_ATTRIBUTES, HTML_TAGS } = require("./utils");
|
||||
const { hasPragma } = require("./pragma");
|
||||
const createError = require("../common/parser-create-error");
|
||||
const { Node } = require("./ast");
|
||||
|
||||
function parse(text, parsers, options, { shouldParseFrontMatter = true } = {}) {
|
||||
function ngHtmlParser(input, canSelfClose) {
|
||||
const parser = require("angular-html-parser");
|
||||
const {
|
||||
RecursiveVisitor,
|
||||
visitAll,
|
||||
Attribute,
|
||||
CDATA,
|
||||
Comment,
|
||||
DocType,
|
||||
Element,
|
||||
Text
|
||||
} = require("angular-html-parser/lib/compiler/src/ml_parser/ast");
|
||||
const {
|
||||
ParseSourceSpan
|
||||
} = require("angular-html-parser/lib/compiler/src/parse_util");
|
||||
const {
|
||||
getHtmlTagDefinition
|
||||
} = require("angular-html-parser/lib/compiler/src/ml_parser/html_tags");
|
||||
|
||||
const { rootNodes, errors } = parser.parse(input, { canSelfClose });
|
||||
|
||||
if (errors.length !== 0) {
|
||||
const { msg, span } = errors[0];
|
||||
const { line, col } = span.start;
|
||||
throw createError(msg, { start: { line: line + 1, column: col } });
|
||||
}
|
||||
|
||||
const addType = node => {
|
||||
if (node instanceof Attribute) {
|
||||
node.type = "attribute";
|
||||
} else if (node instanceof CDATA) {
|
||||
node.type = "cdata";
|
||||
} else if (node instanceof Comment) {
|
||||
node.type = "comment";
|
||||
} else if (node instanceof DocType) {
|
||||
node.type = "docType";
|
||||
} else if (node instanceof Element) {
|
||||
node.type = "element";
|
||||
} else if (node instanceof Text) {
|
||||
node.type = "text";
|
||||
} else {
|
||||
throw new Error(`Unexpected node ${JSON.stringify(node)}`);
|
||||
}
|
||||
};
|
||||
|
||||
const restoreName = node => {
|
||||
const namespace = node.name.startsWith(":")
|
||||
? node.name.slice(1).split(":")[0]
|
||||
: null;
|
||||
const rawName = node.nameSpan ? node.nameSpan.toString() : node.name;
|
||||
const hasExplicitNamespace = rawName.startsWith(`${namespace}:`);
|
||||
const name = hasExplicitNamespace
|
||||
? rawName.slice(namespace.length + 1)
|
||||
: rawName;
|
||||
|
||||
node.name = name;
|
||||
node.namespace = namespace;
|
||||
node.hasExplicitNamespace = hasExplicitNamespace;
|
||||
};
|
||||
|
||||
const restoreNameAndValue = node => {
|
||||
if (node instanceof Element) {
|
||||
restoreName(node);
|
||||
node.attrs.forEach(attr => {
|
||||
restoreName(attr);
|
||||
if (!attr.valueSpan) {
|
||||
attr.value = null;
|
||||
} else {
|
||||
attr.value = attr.valueSpan.toString();
|
||||
if (/['"]/.test(attr.value[0])) {
|
||||
attr.value = attr.value.slice(1, -1);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (node instanceof Comment) {
|
||||
node.value = node.sourceSpan
|
||||
.toString()
|
||||
.slice("<!--".length, -"-->".length);
|
||||
} else if (node instanceof Text) {
|
||||
node.value = node.sourceSpan.toString();
|
||||
}
|
||||
};
|
||||
|
||||
const lowerCaseIfFn = (text, fn) => {
|
||||
const lowerCasedText = text.toLowerCase();
|
||||
return fn(lowerCasedText) ? lowerCasedText : text;
|
||||
};
|
||||
const normalizeName = node => {
|
||||
if (node instanceof Element) {
|
||||
if (
|
||||
!node.namespace ||
|
||||
node.namespace === node.tagDefinition.implicitNamespacePrefix
|
||||
) {
|
||||
node.name = lowerCaseIfFn(
|
||||
node.name,
|
||||
lowerCasedName => lowerCasedName in HTML_TAGS
|
||||
);
|
||||
}
|
||||
|
||||
const CURRENT_HTML_ELEMENT_ATTRIBUTES =
|
||||
HTML_ELEMENT_ATTRIBUTES[node.name] || Object.create(null);
|
||||
node.attrs.forEach(attr => {
|
||||
if (!attr.namespace) {
|
||||
attr.name = lowerCaseIfFn(
|
||||
attr.name,
|
||||
lowerCasedAttrName =>
|
||||
node.name in HTML_ELEMENT_ATTRIBUTES &&
|
||||
(lowerCasedAttrName in HTML_ELEMENT_ATTRIBUTES["*"] ||
|
||||
lowerCasedAttrName in CURRENT_HTML_ELEMENT_ATTRIBUTES)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const fixSourceSpan = node => {
|
||||
if (node.sourceSpan && node.endSourceSpan) {
|
||||
node.sourceSpan = new ParseSourceSpan(
|
||||
node.sourceSpan.start,
|
||||
node.endSourceSpan.end
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const addTagDefinition = node => {
|
||||
if (node instanceof Element) {
|
||||
const tagDefinition = getHtmlTagDefinition(node.name);
|
||||
if (
|
||||
!node.namespace ||
|
||||
node.namespace === tagDefinition.implicitNamespacePrefix
|
||||
) {
|
||||
node.tagDefinition = tagDefinition;
|
||||
} else {
|
||||
node.tagDefinition = getHtmlTagDefinition(""); // the default one
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
visitAll(
|
||||
new class extends RecursiveVisitor {
|
||||
visit(node) {
|
||||
addType(node);
|
||||
restoreNameAndValue(node);
|
||||
addTagDefinition(node);
|
||||
normalizeName(node);
|
||||
fixSourceSpan(node);
|
||||
}
|
||||
}(),
|
||||
rootNodes
|
||||
);
|
||||
|
||||
return rootNodes;
|
||||
}
|
||||
|
||||
function _parse(
|
||||
text,
|
||||
options,
|
||||
recognizeSelfClosing = false,
|
||||
shouldParseFrontMatter = true
|
||||
) {
|
||||
const { frontMatter, content } = shouldParseFrontMatter
|
||||
? parseFrontMatter(text)
|
||||
: { frontMatter: null, content: text };
|
||||
|
||||
// Inline the require to avoid loading all the JS if we don't use it
|
||||
const Parser = require("htmlparser2/lib/Parser");
|
||||
const DomHandler = require("domhandler");
|
||||
|
||||
/**
|
||||
* modifications:
|
||||
* - empty attributes (e.g., `<tag attr>`) are parsed as `{ [attr]: null }` instead of `{ [attr]: "" }`
|
||||
* - trigger `Handler#onselfclosingtag()`
|
||||
*/
|
||||
class CustomParser extends Parser {
|
||||
constructor(cbs, options) {
|
||||
super(cbs, options);
|
||||
this._attribvalue = null;
|
||||
}
|
||||
onattribdata(value) {
|
||||
if (this._attribvalue === null) {
|
||||
this._attribvalue = "";
|
||||
}
|
||||
super.onattribdata(value);
|
||||
}
|
||||
onattribend() {
|
||||
if (this._cbs.onattribute) {
|
||||
this._cbs.onattribute(this._attribname, this._attribvalue);
|
||||
}
|
||||
if (this._attribs) {
|
||||
this._attribs.push([this._attribname, this._attribvalue]);
|
||||
}
|
||||
this._attribname = "";
|
||||
this._attribvalue = null;
|
||||
}
|
||||
onselfclosingtag() {
|
||||
if (this._options.xmlMode || this._options.recognizeSelfClosing) {
|
||||
const name = this._tagname;
|
||||
this.onopentagend();
|
||||
if (this._stack[this._stack.length - 1] === name) {
|
||||
this._cbs.onselfclosingtag();
|
||||
this._cbs.onclosetag(name);
|
||||
this._stack.pop();
|
||||
}
|
||||
} else {
|
||||
this.onopentagend();
|
||||
}
|
||||
}
|
||||
onopentagname(name) {
|
||||
super.onopentagname(name);
|
||||
if (this._cbs.onopentag) {
|
||||
this._attribs = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* modifications:
|
||||
* - add `isSelfClosing` field
|
||||
* - correct `endIndex` for whitespaces before closing tag end marker (e.g., `<x></x\n>`)
|
||||
*/
|
||||
class CustomDomHandler extends DomHandler {
|
||||
onselfclosingtag() {
|
||||
this._tagStack[this._tagStack.length - 1].isSelfClosing = true;
|
||||
}
|
||||
onclosetag() {
|
||||
const elem = this._tagStack.pop();
|
||||
if (this._options.withEndIndices && elem) {
|
||||
const buffer = this._parser._tokenizer._buffer;
|
||||
let endIndex = this._parser.endIndex;
|
||||
while (buffer[endIndex] && buffer[endIndex] !== ">") {
|
||||
endIndex++;
|
||||
}
|
||||
elem.endIndex = buffer[endIndex] ? endIndex : this._parser.endIndex;
|
||||
}
|
||||
if (this._elementCB) {
|
||||
this._elementCB(elem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handler = new CustomDomHandler({
|
||||
withStartIndices: true,
|
||||
withEndIndices: true
|
||||
});
|
||||
|
||||
new CustomParser(handler, {
|
||||
lowerCaseTags: true, // preserve lowercase tag names to avoid false check in htmlparser2 and apply the lowercasing later
|
||||
lowerCaseAttributeNames: false,
|
||||
recognizeSelfClosing: true
|
||||
}).end(content);
|
||||
|
||||
const ast = normalize(
|
||||
{
|
||||
const rawAst = {
|
||||
type: "root",
|
||||
children: handler.dom,
|
||||
startIndex: 0,
|
||||
endIndex: text.length
|
||||
},
|
||||
text
|
||||
);
|
||||
sourceSpan: { start: { offset: 0 }, end: { offset: text.length } },
|
||||
children: ngHtmlParser(content, recognizeSelfClosing)
|
||||
};
|
||||
|
||||
if (frontMatter) {
|
||||
ast.children.unshift(frontMatter);
|
||||
rawAst.children.unshift(frontMatter);
|
||||
}
|
||||
|
||||
const parseHtml = data =>
|
||||
parse(data, parsers, options, {
|
||||
shouldParseFrontMatter: false
|
||||
});
|
||||
const ast = new Node(rawAst);
|
||||
|
||||
return mapNode(ast, node => {
|
||||
const ieConditionalComment = parseIeConditionalComment(node, parseHtml);
|
||||
return ieConditionalComment ? ieConditionalComment : node;
|
||||
const parseSubHtml = (subContent, startSpan) => {
|
||||
const { offset } = startSpan;
|
||||
const fakeContent = text.slice(0, offset).replace(/[^\r\n]/g, " ");
|
||||
const realContent = subContent;
|
||||
const subAst = _parse(
|
||||
fakeContent + realContent,
|
||||
options,
|
||||
recognizeSelfClosing,
|
||||
false
|
||||
);
|
||||
const ParseSourceSpan = subAst.children[0].sourceSpan.constructor;
|
||||
subAst.sourceSpan = new ParseSourceSpan(
|
||||
startSpan,
|
||||
subAst.children[subAst.children.length - 1].sourceSpan.end
|
||||
);
|
||||
const firstText = subAst.children[0];
|
||||
if (firstText.length === offset) {
|
||||
subAst.children.shift();
|
||||
} else {
|
||||
firstText.sourceSpan = new ParseSourceSpan(
|
||||
firstText.sourceSpan.start.moveBy(offset),
|
||||
firstText.sourceSpan.end
|
||||
);
|
||||
firstText.value = firstText.value.slice(offset);
|
||||
}
|
||||
return subAst;
|
||||
};
|
||||
|
||||
const isFakeElement = node => node.type === "element" && !node.nameSpan;
|
||||
return ast.map(node => {
|
||||
if (node.children && node.children.some(isFakeElement)) {
|
||||
const newChildren = [];
|
||||
|
||||
for (const child of node.children) {
|
||||
if (isFakeElement(child)) {
|
||||
Array.prototype.push.apply(newChildren, child.children);
|
||||
} else {
|
||||
newChildren.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
return node.clone({ children: newChildren });
|
||||
}
|
||||
|
||||
if (node.type === "comment") {
|
||||
const ieConditionalComment = parseIeConditionalComment(
|
||||
node,
|
||||
parseSubHtml
|
||||
);
|
||||
if (ieConditionalComment) {
|
||||
return ieConditionalComment;
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
});
|
||||
}
|
||||
|
||||
function parseIeConditionalComment(node, parseHtml) {
|
||||
if (node.type !== "comment") {
|
||||
if (!node.value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const match = node.data.match(/^(\[if([^\]]*?)\]>)([\s\S]*?)<!\s*\[endif\]$/);
|
||||
const match = node.value.match(
|
||||
/^(\[if([^\]]*?)\]>)([\s\S]*?)<!\s*\[endif\]$/
|
||||
);
|
||||
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [, openingTagSuffix, condition, data] = match;
|
||||
const subTree = parseHtml(data);
|
||||
const baseIndex = node.startIndex + "<!--".length + openingTagSuffix.length;
|
||||
|
||||
return Object.assign(
|
||||
{},
|
||||
mapNode(subTree, currentNode =>
|
||||
Object.assign({}, currentNode, {
|
||||
startIndex: baseIndex + currentNode.startIndex,
|
||||
endIndex: baseIndex + currentNode.endIndex
|
||||
})
|
||||
),
|
||||
{
|
||||
const offset = "<!--".length + openingTagSuffix.length;
|
||||
const contentStartSpan = node.sourceSpan.start.moveBy(offset);
|
||||
const ParseSourceSpan = node.sourceSpan.constructor;
|
||||
return Object.assign(parseHtml(data, contentStartSpan), {
|
||||
type: "ieConditionalComment",
|
||||
condition: condition.trim().replace(/\s+/g, " ")
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function normalize(node, text) {
|
||||
delete node.parent;
|
||||
delete node.next;
|
||||
delete node.prev;
|
||||
|
||||
if (node.type === "tag" && !(node.name in HTML_TAGS)) {
|
||||
node.name = text.slice(
|
||||
node.startIndex + 1, // <
|
||||
node.startIndex + 1 + node.name.length
|
||||
);
|
||||
}
|
||||
|
||||
if (node.attribs) {
|
||||
const CURRENT_HTML_ELEMENT_ATTRIBUTES =
|
||||
HTML_ELEMENT_ATTRIBUTES[node.name] || Object.create(null);
|
||||
const attributes = node.attribs.map(([attributeKey, attributeValue]) => {
|
||||
const lowerCaseAttributeKey = attributeKey.toLowerCase();
|
||||
return {
|
||||
type: "attribute",
|
||||
key:
|
||||
lowerCaseAttributeKey in HTML_ELEMENT_ATTRIBUTES["*"] ||
|
||||
lowerCaseAttributeKey in CURRENT_HTML_ELEMENT_ATTRIBUTES
|
||||
? lowerCaseAttributeKey
|
||||
: attributeKey,
|
||||
value: attributeValue
|
||||
};
|
||||
condition: condition.trim().replace(/\s+/g, " "),
|
||||
startSourceSpan: new ParseSourceSpan(
|
||||
node.sourceSpan.start,
|
||||
contentStartSpan
|
||||
),
|
||||
endSourceSpan: new ParseSourceSpan(
|
||||
contentStartSpan.moveBy(data.length),
|
||||
node.sourceSpan.end
|
||||
)
|
||||
});
|
||||
|
||||
const attribs = Object.create(null);
|
||||
for (const attribute of attributes) {
|
||||
attribs[attribute.key] = attribute.value;
|
||||
}
|
||||
|
||||
node.attribs = attribs;
|
||||
node.attributes = attributes;
|
||||
}
|
||||
|
||||
if (node.children) {
|
||||
node.children = node.children.map(child => normalize(child, text));
|
||||
}
|
||||
|
||||
if (
|
||||
node.type === "tag" &&
|
||||
node.name === "textarea" &&
|
||||
node.children.length === 1 &&
|
||||
node.children[0].type === "text" &&
|
||||
node.children[0].data === "\n" &&
|
||||
!/<\/textarea>$/.test(text.slice(locStart(node), locEnd(node)))
|
||||
) {
|
||||
node.children = [];
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
function locStart(node) {
|
||||
return node.startIndex;
|
||||
return node.sourceSpan.start.offset;
|
||||
}
|
||||
|
||||
function locEnd(node) {
|
||||
return node.endIndex + 1;
|
||||
return node.sourceSpan.end.offset;
|
||||
}
|
||||
|
||||
function createParser({ recognizeSelfClosing }) {
|
||||
return {
|
||||
parse: (text, parsers, options) =>
|
||||
_parse(text, options, recognizeSelfClosing),
|
||||
hasPragma,
|
||||
astFormat: "html",
|
||||
locStart,
|
||||
locEnd
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
parsers: {
|
||||
html: {
|
||||
parse,
|
||||
astFormat: "htmlparser2",
|
||||
locStart,
|
||||
locEnd
|
||||
}
|
||||
html: createParser({ recognizeSelfClosing: false }),
|
||||
angular: createParser({ recognizeSelfClosing: false }),
|
||||
vue: createParser({ recognizeSelfClosing: true })
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,27 +1,24 @@
|
|||
"use strict";
|
||||
|
||||
const {
|
||||
VOID_TAGS,
|
||||
canHaveInterpolation,
|
||||
getNodeCssStyleDisplay,
|
||||
getNodeCssStyleWhiteSpace,
|
||||
getPrevNode,
|
||||
isDanglingSpaceSensitiveNode,
|
||||
isIndentationSensitiveNode,
|
||||
isLeadingSpaceSensitiveNode,
|
||||
isScriptLikeTag,
|
||||
isTrailingSpaceSensitiveNode,
|
||||
mapNode
|
||||
isWhitespaceSensitiveNode
|
||||
} = require("./utils");
|
||||
const LineAndColumn = (m => m.default || m)(require("lines-and-columns"));
|
||||
|
||||
const PREPROCESS_PIPELINE = [
|
||||
renameScriptAndStyleWithTag,
|
||||
processDirectives,
|
||||
addIsSelfClosing,
|
||||
removeIgnorableFirstLf,
|
||||
mergeCdataIntoText,
|
||||
extractInterpolation,
|
||||
extractWhitespaces,
|
||||
addCssDisplay,
|
||||
addIsSelfClosing,
|
||||
addIsSpaceSensitive,
|
||||
addStartAndEndLocation,
|
||||
addShortcuts
|
||||
mergeSimpleElementIntoText
|
||||
];
|
||||
|
||||
function preprocess(ast, options) {
|
||||
|
@ -31,59 +28,207 @@ function preprocess(ast, options) {
|
|||
return ast;
|
||||
}
|
||||
|
||||
/** add `startLocation` and `endLocation` field */
|
||||
function addStartAndEndLocation(ast, options) {
|
||||
const locator = new LineAndColumn(options.originalText);
|
||||
return mapNode(ast, node => {
|
||||
const startLocation = locator.locationForIndex(options.locStart(node));
|
||||
const endLocation = locator.locationForIndex(options.locEnd(node) - 1);
|
||||
return Object.assign({}, node, { startLocation, endLocation });
|
||||
});
|
||||
}
|
||||
|
||||
/** rename `script` and `style` with `tag` */
|
||||
function renameScriptAndStyleWithTag(ast /*, options */) {
|
||||
return mapNode(ast, node => {
|
||||
return node.type === "script" || node.type === "style"
|
||||
? Object.assign({}, node, { type: "tag" })
|
||||
: node;
|
||||
});
|
||||
}
|
||||
|
||||
/** add `isSelfClosing` for void tags, directives, and comments */
|
||||
function addIsSelfClosing(ast /*, options */) {
|
||||
return mapNode(ast, node => {
|
||||
function removeIgnorableFirstLf(ast /*, options */) {
|
||||
return ast.map(node => {
|
||||
if (
|
||||
(node.type === "tag" && node.name in VOID_TAGS) ||
|
||||
node.type === "directive" ||
|
||||
node.type === "comment"
|
||||
node.type === "element" &&
|
||||
node.tagDefinition.ignoreFirstLf &&
|
||||
node.children.length !== 0 &&
|
||||
node.children[0].type === "text" &&
|
||||
node.children[0].value[0] === "\n"
|
||||
) {
|
||||
return Object.assign({}, node, { isSelfClosing: true });
|
||||
const text = node.children[0];
|
||||
return node.clone({
|
||||
children:
|
||||
text.value.length === 1
|
||||
? node.children.slice(1)
|
||||
: [].concat(
|
||||
text.clone({ value: text.value.slice(1) }),
|
||||
node.children.slice(1)
|
||||
)
|
||||
});
|
||||
}
|
||||
return node;
|
||||
});
|
||||
}
|
||||
|
||||
function processDirectives(ast /*, options */) {
|
||||
return mapNode(ast, node => {
|
||||
if (node.type !== "directive") {
|
||||
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 ||
|
||||
newChildren[newChildren.length - 1].type !== "text"
|
||||
) {
|
||||
newChildren.push(newChild);
|
||||
continue;
|
||||
}
|
||||
|
||||
const lastChild = newChildren.pop();
|
||||
const ParseSourceSpan = lastChild.sourceSpan.constructor;
|
||||
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" &&
|
||||
// \xA0: non-breaking whitespace
|
||||
!/[^\S\xA0]/.test(node.children[0].value) &&
|
||||
!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 ParseSourceSpan = node.sourceSpan.constructor;
|
||||
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;
|
||||
}
|
||||
|
||||
const interpolationRegex = /\{\{([\s\S]+?)\}\}/g;
|
||||
return ast.map(node => {
|
||||
if (!canHaveInterpolation(node)) {
|
||||
return node;
|
||||
}
|
||||
|
||||
const isDoctype = /^!doctype$/i.test(node.name);
|
||||
const data = node.data.slice(node.name.length).replace(/\s+/g, " ");
|
||||
const newChildren = [];
|
||||
|
||||
return Object.assign({}, node, {
|
||||
name: isDoctype ? "!DOCTYPE" : node.name,
|
||||
data: isDoctype ? data.replace(/^\s+html/i, " html") : data,
|
||||
// workaround for htmlparser2 bug
|
||||
endIndex:
|
||||
node.startIndex +
|
||||
"<".length +
|
||||
node.name.length +
|
||||
node.data.length +
|
||||
">".length
|
||||
for (const child of node.children) {
|
||||
if (child.type !== "text") {
|
||||
newChildren.push(child);
|
||||
continue;
|
||||
}
|
||||
|
||||
const ParseSourceSpan = child.sourceSpan.constructor;
|
||||
|
||||
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) {
|
||||
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 });
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -91,12 +236,12 @@ function processDirectives(ast /*, options */) {
|
|||
* - add `hasLeadingSpaces` field
|
||||
* - add `hasTrailingSpaces` field
|
||||
* - add `hasDanglingSpaces` field for parent nodes
|
||||
* - add `isWhiteSpaceSensitive`, `isIndentationSensitive` field for text nodes
|
||||
* - add `isWhitespaceSensitive`, `isIndentationSensitive` field for text nodes
|
||||
* - remove insensitive whitespaces
|
||||
*/
|
||||
function extractWhitespaces(ast /*, options*/) {
|
||||
const TYPE_WHITESPACE = "whitespace";
|
||||
return mapNode(ast, node => {
|
||||
return ast.map(node => {
|
||||
if (!node.children) {
|
||||
return node;
|
||||
}
|
||||
|
@ -105,19 +250,18 @@ function extractWhitespaces(ast /*, options*/) {
|
|||
node.children.length === 0 ||
|
||||
(node.children.length === 1 &&
|
||||
node.children[0].type === "text" &&
|
||||
node.children[0].data.trim().length === 0)
|
||||
node.children[0].value.trim().length === 0)
|
||||
) {
|
||||
return Object.assign({}, node, {
|
||||
return node.clone({
|
||||
children: [],
|
||||
hasDanglingSpaces: node.children.length !== 0
|
||||
});
|
||||
}
|
||||
|
||||
const cssStyleWhiteSpace = getNodeCssStyleWhiteSpace(node);
|
||||
const isCssStyleWhiteSpacePre = cssStyleWhiteSpace.startsWith("pre");
|
||||
const isScriptLike = isScriptLikeTag(node);
|
||||
const isWhitespaceSensitive = isWhitespaceSensitiveNode(node);
|
||||
const isIndentationSensitive = isIndentationSensitiveNode(node);
|
||||
|
||||
return Object.assign({}, node, {
|
||||
return node.clone({
|
||||
children: node.children
|
||||
// extract whitespace nodes
|
||||
.reduce((newChildren, child) => {
|
||||
|
@ -125,18 +269,18 @@ function extractWhitespaces(ast /*, options*/) {
|
|||
return newChildren.concat(child);
|
||||
}
|
||||
|
||||
if (isCssStyleWhiteSpacePre || isScriptLike) {
|
||||
if (isWhitespaceSensitive) {
|
||||
return newChildren.concat(
|
||||
Object.assign({}, child, {
|
||||
isWhiteSpaceSensitive: true,
|
||||
isIndentationSensitive: isCssStyleWhiteSpacePre
|
||||
isWhitespaceSensitive,
|
||||
isIndentationSensitive
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const localChildren = [];
|
||||
|
||||
const [, leadingSpaces, text, trailingSpaces] = child.data.match(
|
||||
const [, leadingSpaces, text, trailingSpaces] = child.value.match(
|
||||
/^(\s*)([\s\S]*?)(\s*)$/
|
||||
);
|
||||
|
||||
|
@ -144,12 +288,16 @@ function extractWhitespaces(ast /*, options*/) {
|
|||
localChildren.push({ type: TYPE_WHITESPACE });
|
||||
}
|
||||
|
||||
const ParseSourceSpan = child.sourceSpan.constructor;
|
||||
|
||||
if (text) {
|
||||
localChildren.push({
|
||||
type: "text",
|
||||
data: text,
|
||||
startIndex: child.startIndex + leadingSpaces.length,
|
||||
endIndex: child.endIndex - trailingSpaces.length
|
||||
value: text,
|
||||
sourceSpan: new ParseSourceSpan(
|
||||
child.sourceSpan.start.moveBy(leadingSpaces.length),
|
||||
child.sourceSpan.end.moveBy(-trailingSpaces.length)
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -182,13 +330,23 @@ function extractWhitespaces(ast /*, options*/) {
|
|||
});
|
||||
}
|
||||
|
||||
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 addCssDisplay(ast, options) {
|
||||
return mapNode(ast, (node, stack) => {
|
||||
const prevNode = getPrevNode(stack);
|
||||
return Object.assign({}, node, {
|
||||
cssDisplay: getNodeCssStyleDisplay(node, prevNode, options)
|
||||
});
|
||||
});
|
||||
return ast.map(node =>
|
||||
Object.assign(node, { cssDisplay: getNodeCssStyleDisplay(node, options) })
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -197,89 +355,41 @@ function addCssDisplay(ast, options) {
|
|||
* - add `isDanglingSpaceSensitive` field for parent nodes
|
||||
*/
|
||||
function addIsSpaceSensitive(ast /*, options */) {
|
||||
return mapNode(ast, node => {
|
||||
return ast.map(node => {
|
||||
if (!node.children) {
|
||||
return node;
|
||||
}
|
||||
|
||||
if (node.children.length === 0) {
|
||||
return Object.assign({}, node, {
|
||||
return node.clone({
|
||||
isDanglingSpaceSensitive: isDanglingSpaceSensitiveNode(node)
|
||||
});
|
||||
}
|
||||
|
||||
return Object.assign({}, node, {
|
||||
return node.clone({
|
||||
children: node.children
|
||||
// set isLeadingSpaceSensitive
|
||||
.map((child, i, children) => {
|
||||
const prevChild = i === 0 ? null : children[i - 1];
|
||||
const nextChild = i === children.length - 1 ? null : children[i + 1];
|
||||
.map(child => {
|
||||
return Object.assign({}, child, {
|
||||
isLeadingSpaceSensitive: isLeadingSpaceSensitiveNode(child, {
|
||||
parent: node,
|
||||
prev: prevChild,
|
||||
next: nextChild
|
||||
})
|
||||
isLeadingSpaceSensitive: isLeadingSpaceSensitiveNode(child),
|
||||
isTrailingSpaceSensitive: isTrailingSpaceSensitiveNode(child)
|
||||
});
|
||||
})
|
||||
// set isTrailingSpaceSensitive and update isLeadingSpaceSensitive if necessary
|
||||
.reduce((newChildren, child, i, children) => {
|
||||
const prevChild = i === 0 ? null : newChildren[i - 1];
|
||||
const nextChild = i === children.length - 1 ? null : children[i + 1];
|
||||
const isTrailingSpaceSensitive =
|
||||
nextChild && !nextChild.isLeadingSpaceSensitive
|
||||
? false
|
||||
: isTrailingSpaceSensitiveNode(child, {
|
||||
parent: node,
|
||||
prev: prevChild,
|
||||
next: nextChild
|
||||
});
|
||||
return newChildren.concat(
|
||||
Object.assign(
|
||||
{},
|
||||
child,
|
||||
{ isTrailingSpaceSensitive },
|
||||
prevChild &&
|
||||
!prevChild.isTrailingSpaceSensitive &&
|
||||
child.isLeadingSpaceSensitive
|
||||
? { isLeadingSpaceSensitive: false }
|
||||
: null
|
||||
.map((child, index, children) =>
|
||||
Object.assign({}, 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
|
||||
})
|
||||
)
|
||||
);
|
||||
}, [])
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function addShortcuts(ast /*, options */) {
|
||||
function _addShortcuts(node, parent, index) {
|
||||
const prev = index === -1 ? null : parent.children[index - 1];
|
||||
const next = index === -1 ? null : parent.children[index + 1];
|
||||
|
||||
const hasChildren = node.children && node.children.length !== 0;
|
||||
|
||||
const firstChild = !hasChildren ? null : node.children[0];
|
||||
const lastChild = !hasChildren
|
||||
? null
|
||||
: node.children[node.children.length - 1];
|
||||
|
||||
Object.defineProperties(node, {
|
||||
parent: { value: parent, enumerable: false },
|
||||
prev: { value: prev, enumerable: false },
|
||||
next: { value: next, enumerable: false },
|
||||
firstChild: { value: firstChild, enumerable: false },
|
||||
lastChild: { value: lastChild, enumerable: false }
|
||||
});
|
||||
|
||||
if (node.children) {
|
||||
node.children.forEach((child, childIndex) =>
|
||||
_addShortcuts(child, node, childIndex)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_addShortcuts(ast, null, -1);
|
||||
return ast;
|
||||
}
|
||||
|
||||
module.exports = preprocess;
|
||||
|
|
|
@ -0,0 +1,931 @@
|
|||
"use strict";
|
||||
|
||||
const clean = require("./clean");
|
||||
const {
|
||||
builders,
|
||||
utils: { stripTrailingHardline, mapDoc }
|
||||
} = require("../doc");
|
||||
const {
|
||||
breakParent,
|
||||
fill,
|
||||
group,
|
||||
hardline,
|
||||
ifBreak,
|
||||
indent,
|
||||
join,
|
||||
line,
|
||||
literalline,
|
||||
markAsRoot,
|
||||
softline
|
||||
} = builders;
|
||||
const {
|
||||
countParents,
|
||||
dedentString,
|
||||
forceBreakChildren,
|
||||
forceBreakContent,
|
||||
forceNextEmptyLine,
|
||||
getCommentData,
|
||||
getLastDescendant,
|
||||
getPrettierIgnoreAttributeCommentData,
|
||||
hasPrettierIgnore,
|
||||
inferScriptParser,
|
||||
isPreLikeNode,
|
||||
isScriptLikeTag,
|
||||
normalizeParts,
|
||||
preferHardlineAsLeadingSpaces,
|
||||
replaceDocNewlines,
|
||||
replaceNewlines
|
||||
} = require("./utils");
|
||||
const preprocess = require("./preprocess");
|
||||
const assert = require("assert");
|
||||
const { insertPragma } = require("./pragma");
|
||||
const { printVueFor, printVueSlotScope } = require("./syntax-vue");
|
||||
const { printImgSrcset } = require("./syntax-attribute");
|
||||
|
||||
function concat(parts) {
|
||||
const newParts = normalizeParts(parts);
|
||||
return newParts.length === 0
|
||||
? ""
|
||||
: newParts.length === 1
|
||||
? newParts[0]
|
||||
: builders.concat(newParts);
|
||||
}
|
||||
|
||||
function embed(path, print, textToDoc, options) {
|
||||
const node = path.getValue();
|
||||
switch (node.type) {
|
||||
case "text": {
|
||||
if (isScriptLikeTag(node.parent)) {
|
||||
const parser = inferScriptParser(node.parent);
|
||||
if (parser) {
|
||||
const value =
|
||||
parser === "markdown"
|
||||
? dedentString(node.value.replace(/^[^\S\n]*?\n/, ""))
|
||||
: node.value;
|
||||
return builders.concat([
|
||||
concat([
|
||||
breakParent,
|
||||
printOpeningTagPrefix(node),
|
||||
markAsRoot(stripTrailingHardline(textToDoc(value, { parser }))),
|
||||
printClosingTagSuffix(node)
|
||||
])
|
||||
]);
|
||||
}
|
||||
} else if (node.parent.type === "interpolation") {
|
||||
return concat([
|
||||
indent(
|
||||
concat([
|
||||
line,
|
||||
textToDoc(
|
||||
node.value,
|
||||
options.parser === "angular"
|
||||
? { parser: "__ng_interpolation", trailingComma: "none" }
|
||||
: options.parser === "vue"
|
||||
? { parser: "__vue_expression" }
|
||||
: { parser: "__js_expression" }
|
||||
)
|
||||
])
|
||||
),
|
||||
node.parent.next &&
|
||||
needsToBorrowPrevClosingTagEndMarker(node.parent.next)
|
||||
? " "
|
||||
: line
|
||||
]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "attribute": {
|
||||
if (!node.value) {
|
||||
break;
|
||||
}
|
||||
|
||||
const embeddedAttributeValueDoc = printEmbeddedAttributeValue(
|
||||
node,
|
||||
(code, opts) =>
|
||||
// strictly prefer single quote to avoid unnecessary html entity escape
|
||||
textToDoc(code, Object.assign({ __isInHtmlAttribute: true }, opts)),
|
||||
options
|
||||
);
|
||||
if (embeddedAttributeValueDoc) {
|
||||
return concat([
|
||||
node.rawName,
|
||||
'="',
|
||||
mapDoc(
|
||||
embeddedAttributeValueDoc,
|
||||
doc => (typeof doc === "string" ? doc.replace(/"/g, """) : doc)
|
||||
),
|
||||
'"'
|
||||
]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "yaml":
|
||||
return markAsRoot(
|
||||
concat([
|
||||
"---",
|
||||
hardline,
|
||||
node.value.trim().length === 0
|
||||
? ""
|
||||
: replaceDocNewlines(
|
||||
textToDoc(node.value, { parser: "yaml" }),
|
||||
literalline
|
||||
),
|
||||
"---"
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function genericPrint(path, options, print) {
|
||||
const node = path.getValue();
|
||||
switch (node.type) {
|
||||
case "root":
|
||||
// use original concat to not break stripTrailingHardline
|
||||
return builders.concat([
|
||||
group(printChildren(path, options, print)),
|
||||
hardline
|
||||
]);
|
||||
case "element":
|
||||
case "ieConditionalComment": {
|
||||
/**
|
||||
* do not break:
|
||||
*
|
||||
* <div>{{
|
||||
* ~
|
||||
* interpolation
|
||||
* }}</div>
|
||||
* ~
|
||||
*
|
||||
* exception: break if the opening tag breaks
|
||||
*
|
||||
* <div
|
||||
* long
|
||||
* ~
|
||||
* >{{
|
||||
* interpolation
|
||||
* }}</div
|
||||
* ~
|
||||
* >
|
||||
*/
|
||||
const shouldHugContent =
|
||||
node.children.length === 1 &&
|
||||
node.firstChild.type === "interpolation" &&
|
||||
(node.firstChild.isLeadingSpaceSensitive &&
|
||||
!node.firstChild.hasLeadingSpaces) &&
|
||||
(node.lastChild.isTrailingSpaceSensitive &&
|
||||
!node.lastChild.hasTrailingSpaces);
|
||||
const attrGroupId = Symbol("element-attr-group-id");
|
||||
return concat([
|
||||
group(
|
||||
concat([
|
||||
group(printOpeningTag(path, options, print), { id: attrGroupId }),
|
||||
node.children.length === 0
|
||||
? node.hasDanglingSpaces && node.isDanglingSpaceSensitive
|
||||
? line
|
||||
: ""
|
||||
: concat([
|
||||
forceBreakContent(node) ? breakParent : "",
|
||||
(childrenDoc =>
|
||||
shouldHugContent
|
||||
? ifBreak(indent(childrenDoc), childrenDoc, {
|
||||
groupId: attrGroupId
|
||||
})
|
||||
: isScriptLikeTag(node) &&
|
||||
node.parent.type === "root" &&
|
||||
options.parser === "vue"
|
||||
? childrenDoc
|
||||
: indent(childrenDoc))(
|
||||
concat([
|
||||
shouldHugContent
|
||||
? ifBreak(softline, "", { groupId: attrGroupId })
|
||||
: node.firstChild.type === "text" &&
|
||||
node.firstChild.isWhitespaceSensitive &&
|
||||
node.firstChild.isIndentationSensitive
|
||||
? node.firstChild.value.indexOf("\n") === -1
|
||||
? ""
|
||||
: literalline
|
||||
: node.firstChild.hasLeadingSpaces &&
|
||||
node.firstChild.isLeadingSpaceSensitive
|
||||
? line
|
||||
: softline,
|
||||
printChildren(path, options, print)
|
||||
])
|
||||
),
|
||||
(node.next
|
||||
? needsToBorrowPrevClosingTagEndMarker(node.next)
|
||||
: needsToBorrowLastChildClosingTagEndMarker(node.parent))
|
||||
? node.lastChild.hasTrailingSpaces &&
|
||||
node.lastChild.isTrailingSpaceSensitive
|
||||
? " "
|
||||
: ""
|
||||
: shouldHugContent
|
||||
? ifBreak(softline, "", { groupId: attrGroupId })
|
||||
: node.lastChild.hasTrailingSpaces &&
|
||||
node.lastChild.isTrailingSpaceSensitive
|
||||
? line
|
||||
: node.type === "element" &&
|
||||
isPreLikeNode(node) &&
|
||||
node.lastChild.type === "text" &&
|
||||
(node.lastChild.value.indexOf("\n") === -1 ||
|
||||
new RegExp(
|
||||
`\\n\\s{${options.tabWidth *
|
||||
countParents(
|
||||
path,
|
||||
n => n.parent && n.parent.type !== "root"
|
||||
)}}$`
|
||||
).test(node.lastChild.value))
|
||||
? /**
|
||||
* <div>
|
||||
* <pre>
|
||||
* something
|
||||
* </pre>
|
||||
* ~
|
||||
* </div>
|
||||
*/
|
||||
""
|
||||
: softline
|
||||
])
|
||||
])
|
||||
),
|
||||
printClosingTag(node)
|
||||
]);
|
||||
}
|
||||
case "interpolation":
|
||||
return concat([
|
||||
printOpeningTagStart(node),
|
||||
concat(path.map(print, "children")),
|
||||
printClosingTagEnd(node)
|
||||
]);
|
||||
case "text": {
|
||||
if (node.parent.type === "interpolation") {
|
||||
// replace the trailing literalline with hardline for better readability
|
||||
const trailingNewlineRegex = /\n[^\S\n]*?$/;
|
||||
const hasTrailingNewline = trailingNewlineRegex.test(node.value);
|
||||
const value = hasTrailingNewline
|
||||
? node.value.replace(trailingNewlineRegex, "")
|
||||
: node.value;
|
||||
return concat([
|
||||
concat(replaceNewlines(value, literalline)),
|
||||
hasTrailingNewline ? hardline : ""
|
||||
]);
|
||||
}
|
||||
return fill(
|
||||
normalizeParts(
|
||||
[].concat(
|
||||
printOpeningTagPrefix(node),
|
||||
getTextValueParts(node),
|
||||
printClosingTagSuffix(node)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
case "docType":
|
||||
return concat([
|
||||
group(
|
||||
concat([
|
||||
printOpeningTagStart(node),
|
||||
" ",
|
||||
node.value.replace(/^html\b/i, "html").replace(/\s+/g, " ")
|
||||
])
|
||||
),
|
||||
printClosingTagEnd(node)
|
||||
]);
|
||||
case "comment": {
|
||||
const value = getCommentData(node);
|
||||
return concat([
|
||||
group(
|
||||
concat([
|
||||
printOpeningTagStart(node),
|
||||
value.trim().length === 0
|
||||
? ""
|
||||
: concat([
|
||||
indent(
|
||||
concat([
|
||||
node.prev &&
|
||||
needsToBorrowNextOpeningTagStartMarker(node.prev)
|
||||
? breakParent
|
||||
: "",
|
||||
line,
|
||||
concat(replaceNewlines(value, hardline))
|
||||
])
|
||||
),
|
||||
(node.next
|
||||
? needsToBorrowPrevClosingTagEndMarker(node.next)
|
||||
: needsToBorrowLastChildClosingTagEndMarker(node.parent))
|
||||
? " "
|
||||
: line
|
||||
])
|
||||
])
|
||||
),
|
||||
printClosingTagEnd(node)
|
||||
]);
|
||||
}
|
||||
case "attribute":
|
||||
return concat([
|
||||
node.rawName,
|
||||
node.value === null
|
||||
? ""
|
||||
: concat([
|
||||
'="',
|
||||
concat(
|
||||
replaceNewlines(node.value.replace(/"/g, """), literalline)
|
||||
),
|
||||
'"'
|
||||
])
|
||||
]);
|
||||
case "yaml":
|
||||
case "toml":
|
||||
return node.raw;
|
||||
default:
|
||||
throw new Error(`Unexpected node type ${node.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
function printChildren(path, options, print) {
|
||||
const node = path.getValue();
|
||||
|
||||
if (forceBreakChildren(node)) {
|
||||
return concat([
|
||||
breakParent,
|
||||
concat(
|
||||
path.map(childPath => {
|
||||
const childNode = childPath.getValue();
|
||||
const prevBetweenLine = !childNode.prev
|
||||
? ""
|
||||
: printBetweenLine(childNode.prev, childNode);
|
||||
return concat([
|
||||
!prevBetweenLine
|
||||
? ""
|
||||
: concat([
|
||||
prevBetweenLine,
|
||||
forceNextEmptyLine(childNode.prev) ? hardline : ""
|
||||
]),
|
||||
printChild(childPath)
|
||||
]);
|
||||
}, "children")
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
const groupIds = node.children.map(() => Symbol(""));
|
||||
return concat(
|
||||
path.map((childPath, childIndex) => {
|
||||
const childNode = childPath.getValue();
|
||||
|
||||
if (childNode.type === "text") {
|
||||
return printChild(childPath);
|
||||
}
|
||||
|
||||
const prevParts = [];
|
||||
const leadingParts = [];
|
||||
const trailingParts = [];
|
||||
const nextParts = [];
|
||||
|
||||
const prevBetweenLine = childNode.prev
|
||||
? printBetweenLine(childNode.prev, childNode)
|
||||
: "";
|
||||
|
||||
const nextBetweenLine = childNode.next
|
||||
? printBetweenLine(childNode, childNode.next)
|
||||
: "";
|
||||
|
||||
if (prevBetweenLine) {
|
||||
if (forceNextEmptyLine(childNode.prev)) {
|
||||
prevParts.push(hardline, hardline);
|
||||
} else if (prevBetweenLine === hardline) {
|
||||
prevParts.push(hardline);
|
||||
} else {
|
||||
if (childNode.prev.type === "text") {
|
||||
leadingParts.push(prevBetweenLine);
|
||||
} else {
|
||||
leadingParts.push(
|
||||
ifBreak("", softline, {
|
||||
groupId: groupIds[childIndex - 1]
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nextBetweenLine) {
|
||||
if (forceNextEmptyLine(childNode)) {
|
||||
if (childNode.next.type === "text") {
|
||||
nextParts.push(hardline, hardline);
|
||||
}
|
||||
} else if (nextBetweenLine === hardline) {
|
||||
if (childNode.next.type === "text") {
|
||||
nextParts.push(hardline);
|
||||
}
|
||||
} else {
|
||||
trailingParts.push(nextBetweenLine);
|
||||
}
|
||||
}
|
||||
|
||||
return concat(
|
||||
[].concat(
|
||||
prevParts,
|
||||
group(
|
||||
concat([
|
||||
concat(leadingParts),
|
||||
group(concat([printChild(childPath), concat(trailingParts)]), {
|
||||
id: groupIds[childIndex]
|
||||
})
|
||||
])
|
||||
),
|
||||
nextParts
|
||||
)
|
||||
);
|
||||
}, "children")
|
||||
);
|
||||
|
||||
function printChild(childPath) {
|
||||
if (!hasPrettierIgnore(childPath)) {
|
||||
return print(childPath);
|
||||
}
|
||||
const child = childPath.getValue();
|
||||
return concat([
|
||||
printOpeningTagPrefix(child),
|
||||
options.originalText.slice(
|
||||
options.locStart(child) +
|
||||
(child.prev && needsToBorrowNextOpeningTagStartMarker(child.prev)
|
||||
? printOpeningTagStartMarker(child).length
|
||||
: 0),
|
||||
options.locEnd(child) -
|
||||
(child.next && needsToBorrowPrevClosingTagEndMarker(child.next)
|
||||
? printClosingTagEndMarker(child).length
|
||||
: 0),
|
||||
printClosingTagSuffix(child)
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
function printBetweenLine(prevNode, nextNode) {
|
||||
return (needsToBorrowNextOpeningTagStartMarker(prevNode) &&
|
||||
/**
|
||||
* 123<a
|
||||
* ~
|
||||
* ><b>
|
||||
*/
|
||||
(nextNode.firstChild ||
|
||||
/**
|
||||
* 123<!--
|
||||
* ~
|
||||
* -->
|
||||
*/
|
||||
nextNode.isSelfClosing ||
|
||||
/**
|
||||
* 123<span
|
||||
* ~
|
||||
* attr
|
||||
*/
|
||||
(nextNode.type === "element" && nextNode.attrs.length !== 0))) ||
|
||||
/**
|
||||
* <img
|
||||
* src="long"
|
||||
* ~
|
||||
* />123
|
||||
*/
|
||||
(prevNode.type === "element" &&
|
||||
prevNode.isSelfClosing &&
|
||||
needsToBorrowPrevClosingTagEndMarker(nextNode))
|
||||
? ""
|
||||
: !nextNode.isLeadingSpaceSensitive ||
|
||||
preferHardlineAsLeadingSpaces(nextNode) ||
|
||||
/**
|
||||
* Want to write us a letter? Use our<a
|
||||
* ><b><a>mailing address</a></b></a
|
||||
* ~
|
||||
* >.
|
||||
*/
|
||||
(needsToBorrowPrevClosingTagEndMarker(nextNode) &&
|
||||
prevNode.lastChild &&
|
||||
needsToBorrowParentClosingTagStartMarker(prevNode.lastChild) &&
|
||||
prevNode.lastChild.lastChild &&
|
||||
needsToBorrowParentClosingTagStartMarker(
|
||||
prevNode.lastChild.lastChild
|
||||
))
|
||||
? hardline
|
||||
: nextNode.hasLeadingSpaces
|
||||
? line
|
||||
: softline;
|
||||
}
|
||||
}
|
||||
|
||||
function printOpeningTag(path, options, print) {
|
||||
const node = path.getValue();
|
||||
const forceNotToBreakAttrContent =
|
||||
node.type === "element" &&
|
||||
node.fullName === "script" &&
|
||||
node.attrs.length === 1 &&
|
||||
node.attrs[0].fullName === "src" &&
|
||||
node.children.length === 0;
|
||||
return concat([
|
||||
printOpeningTagStart(node),
|
||||
!node.attrs || node.attrs.length === 0
|
||||
? node.isSelfClosing
|
||||
? /**
|
||||
* <br />
|
||||
* ^
|
||||
*/
|
||||
" "
|
||||
: ""
|
||||
: concat([
|
||||
indent(
|
||||
concat([
|
||||
forceNotToBreakAttrContent ? " " : line,
|
||||
join(
|
||||
line,
|
||||
(ignoreAttributeData => {
|
||||
const hasPrettierIgnoreAttribute =
|
||||
typeof ignoreAttributeData === "boolean"
|
||||
? () => ignoreAttributeData
|
||||
: Array.isArray(ignoreAttributeData)
|
||||
? attr =>
|
||||
ignoreAttributeData.indexOf(attr.rawName) !== -1
|
||||
: () => false;
|
||||
return path.map(attrPath => {
|
||||
const attr = attrPath.getValue();
|
||||
return hasPrettierIgnoreAttribute(attr)
|
||||
? options.originalText.slice(
|
||||
options.locStart(attr),
|
||||
options.locEnd(attr)
|
||||
)
|
||||
: print(attrPath);
|
||||
}, "attrs");
|
||||
})(
|
||||
node.prev &&
|
||||
node.prev.type === "comment" &&
|
||||
getPrettierIgnoreAttributeCommentData(node.prev.value)
|
||||
)
|
||||
)
|
||||
])
|
||||
),
|
||||
/**
|
||||
* 123<a
|
||||
* attr
|
||||
* ~
|
||||
* >456
|
||||
*/
|
||||
(node.firstChild &&
|
||||
needsToBorrowParentOpeningTagEndMarker(node.firstChild)) ||
|
||||
/**
|
||||
* <span
|
||||
* >123<meta
|
||||
* ~
|
||||
* /></span>
|
||||
*/
|
||||
(node.isSelfClosing &&
|
||||
needsToBorrowLastChildClosingTagEndMarker(node.parent))
|
||||
? ""
|
||||
: node.isSelfClosing
|
||||
? forceNotToBreakAttrContent
|
||||
? " "
|
||||
: line
|
||||
: forceNotToBreakAttrContent
|
||||
? ""
|
||||
: softline
|
||||
]),
|
||||
node.isSelfClosing ? "" : printOpeningTagEnd(node)
|
||||
]);
|
||||
}
|
||||
|
||||
function printOpeningTagStart(node) {
|
||||
return node.prev && needsToBorrowNextOpeningTagStartMarker(node.prev)
|
||||
? ""
|
||||
: concat([printOpeningTagPrefix(node), printOpeningTagStartMarker(node)]);
|
||||
}
|
||||
|
||||
function printOpeningTagEnd(node) {
|
||||
return node.firstChild &&
|
||||
needsToBorrowParentOpeningTagEndMarker(node.firstChild)
|
||||
? ""
|
||||
: printOpeningTagEndMarker(node);
|
||||
}
|
||||
|
||||
function printClosingTag(node) {
|
||||
return concat([
|
||||
node.isSelfClosing ? "" : printClosingTagStart(node),
|
||||
printClosingTagEnd(node)
|
||||
]);
|
||||
}
|
||||
|
||||
function printClosingTagStart(node) {
|
||||
return node.lastChild &&
|
||||
needsToBorrowParentClosingTagStartMarker(node.lastChild)
|
||||
? ""
|
||||
: concat([printClosingTagPrefix(node), printClosingTagStartMarker(node)]);
|
||||
}
|
||||
|
||||
function printClosingTagEnd(node) {
|
||||
return (node.next
|
||||
? needsToBorrowPrevClosingTagEndMarker(node.next)
|
||||
: needsToBorrowLastChildClosingTagEndMarker(node.parent))
|
||||
? ""
|
||||
: concat([printClosingTagEndMarker(node), printClosingTagSuffix(node)]);
|
||||
}
|
||||
|
||||
function needsToBorrowNextOpeningTagStartMarker(node) {
|
||||
/**
|
||||
* 123<p
|
||||
* ^^
|
||||
* >
|
||||
*/
|
||||
return (
|
||||
node.next &&
|
||||
node.type === "text" &&
|
||||
node.isTrailingSpaceSensitive &&
|
||||
!node.hasTrailingSpaces
|
||||
);
|
||||
}
|
||||
|
||||
function needsToBorrowParentOpeningTagEndMarker(node) {
|
||||
/**
|
||||
* <p
|
||||
* >123
|
||||
* ^
|
||||
*
|
||||
* <p
|
||||
* ><a
|
||||
* ^
|
||||
*/
|
||||
return !node.prev && node.isLeadingSpaceSensitive && !node.hasLeadingSpaces;
|
||||
}
|
||||
|
||||
function needsToBorrowPrevClosingTagEndMarker(node) {
|
||||
/**
|
||||
* <p></p
|
||||
* >123
|
||||
* ^
|
||||
*
|
||||
* <p></p
|
||||
* ><a
|
||||
* ^
|
||||
*/
|
||||
return node.prev && node.isLeadingSpaceSensitive && !node.hasLeadingSpaces;
|
||||
}
|
||||
|
||||
function needsToBorrowLastChildClosingTagEndMarker(node) {
|
||||
/**
|
||||
* <p
|
||||
* ><a></a
|
||||
* ></p
|
||||
* ^
|
||||
* >
|
||||
*/
|
||||
return (
|
||||
node.lastChild &&
|
||||
node.lastChild.isTrailingSpaceSensitive &&
|
||||
!node.lastChild.hasTrailingSpaces &&
|
||||
getLastDescendant(node.lastChild).type !== "text"
|
||||
);
|
||||
}
|
||||
|
||||
function needsToBorrowParentClosingTagStartMarker(node) {
|
||||
/**
|
||||
* <p>
|
||||
* 123</p
|
||||
* ^^^
|
||||
* >
|
||||
*
|
||||
* 123</b
|
||||
* ></a
|
||||
* ^^^
|
||||
* >
|
||||
*/
|
||||
return (
|
||||
!node.next &&
|
||||
!node.hasTrailingSpaces &&
|
||||
node.isTrailingSpaceSensitive &&
|
||||
getLastDescendant(node).type === "text"
|
||||
);
|
||||
}
|
||||
|
||||
function printOpeningTagPrefix(node) {
|
||||
return needsToBorrowParentOpeningTagEndMarker(node)
|
||||
? printOpeningTagEndMarker(node.parent)
|
||||
: needsToBorrowPrevClosingTagEndMarker(node)
|
||||
? printClosingTagEndMarker(node.prev)
|
||||
: "";
|
||||
}
|
||||
|
||||
function printClosingTagPrefix(node) {
|
||||
return needsToBorrowLastChildClosingTagEndMarker(node)
|
||||
? printClosingTagEndMarker(node.lastChild)
|
||||
: "";
|
||||
}
|
||||
|
||||
function printClosingTagSuffix(node) {
|
||||
return needsToBorrowParentClosingTagStartMarker(node)
|
||||
? printClosingTagStartMarker(node.parent)
|
||||
: needsToBorrowNextOpeningTagStartMarker(node)
|
||||
? printOpeningTagStartMarker(node.next)
|
||||
: "";
|
||||
}
|
||||
|
||||
function printOpeningTagStartMarker(node) {
|
||||
switch (node.type) {
|
||||
case "comment":
|
||||
return "<!--";
|
||||
case "ieConditionalComment":
|
||||
return `<!--[if ${node.condition}`;
|
||||
case "interpolation":
|
||||
return "{{";
|
||||
case "docType":
|
||||
return "<!DOCTYPE";
|
||||
default:
|
||||
return `<${node.rawName}`;
|
||||
}
|
||||
}
|
||||
|
||||
function printOpeningTagEndMarker(node) {
|
||||
assert(!node.isSelfClosing);
|
||||
switch (node.type) {
|
||||
case "ieConditionalComment":
|
||||
return "]>";
|
||||
default:
|
||||
return `>`;
|
||||
}
|
||||
}
|
||||
|
||||
function printClosingTagStartMarker(node) {
|
||||
assert(!node.isSelfClosing);
|
||||
switch (node.type) {
|
||||
case "ieConditionalComment":
|
||||
return "<!";
|
||||
default:
|
||||
return `</${node.rawName}`;
|
||||
}
|
||||
}
|
||||
|
||||
function printClosingTagEndMarker(node) {
|
||||
switch (node.type) {
|
||||
case "comment":
|
||||
return "-->";
|
||||
case "ieConditionalComment":
|
||||
return `[endif]-->`;
|
||||
case "interpolation":
|
||||
return "}}";
|
||||
case "element":
|
||||
if (node.isSelfClosing) {
|
||||
return "/>";
|
||||
}
|
||||
// fall through
|
||||
default:
|
||||
return ">";
|
||||
}
|
||||
}
|
||||
|
||||
function getTextValueParts(node, value = node.value) {
|
||||
return node.isWhitespaceSensitive
|
||||
? node.isIndentationSensitive
|
||||
? replaceNewlines(value, literalline)
|
||||
: replaceNewlines(
|
||||
dedentString(value.replace(/^\s*?\n|\n\s*?$/g, "")),
|
||||
hardline
|
||||
)
|
||||
: // non-breaking whitespace: 0xA0
|
||||
join(line, value.split(/[^\S\xA0]+/)).parts;
|
||||
}
|
||||
|
||||
function printEmbeddedAttributeValue(node, originalTextToDoc, options) {
|
||||
const isKeyMatched = patterns =>
|
||||
new RegExp(patterns.join("|")).test(node.fullName);
|
||||
const getValue = () =>
|
||||
node.value.replace(/"/g, '"').replace(/'/g, "'");
|
||||
|
||||
let shouldHug = false;
|
||||
|
||||
const __onHtmlBindingRoot = root => {
|
||||
const rootNode =
|
||||
root.type === "NGRoot"
|
||||
? root.node.type === "NGMicrosyntax" &&
|
||||
root.node.body.length === 1 &&
|
||||
root.node.body[0].type === "NGMicrosyntaxExpression"
|
||||
? root.node.body[0].expression
|
||||
: root.node
|
||||
: root.type === "JsExpressionRoot"
|
||||
? root.node
|
||||
: root;
|
||||
if (
|
||||
rootNode &&
|
||||
(rootNode.type === "ObjectExpression" ||
|
||||
rootNode.type === "ArrayExpression")
|
||||
) {
|
||||
shouldHug = true;
|
||||
}
|
||||
};
|
||||
|
||||
const printHug = doc => group(doc);
|
||||
const printExpand = doc =>
|
||||
group(concat([indent(concat([softline, doc])), softline]));
|
||||
const printMaybeHug = doc => (shouldHug ? printHug(doc) : printExpand(doc));
|
||||
|
||||
const textToDoc = (code, opts) =>
|
||||
originalTextToDoc(code, Object.assign({ __onHtmlBindingRoot }, opts));
|
||||
|
||||
if (
|
||||
node.fullName === "srcset" &&
|
||||
(node.parent.fullName === "img" || node.parent.fullName === "source")
|
||||
) {
|
||||
return printExpand(printImgSrcset(getValue()));
|
||||
}
|
||||
|
||||
if (options.parser === "vue") {
|
||||
if (node.fullName === "v-for") {
|
||||
return printVueFor(getValue(), textToDoc);
|
||||
}
|
||||
|
||||
if (node.fullName === "slot-scope") {
|
||||
return printVueSlotScope(getValue(), textToDoc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @click="jsStatement"
|
||||
* @click="jsExpression"
|
||||
* v-on:click="jsStatement"
|
||||
* v-on:click="jsExpression"
|
||||
*/
|
||||
const vueEventBindingPatterns = ["^@", "^v-on:"];
|
||||
/**
|
||||
* :class="vueExpression"
|
||||
* v-bind:id="vueExpression"
|
||||
*/
|
||||
const vueExpressionBindingPatterns = ["^:", "^v-bind:"];
|
||||
/**
|
||||
* v-if="jsExpression"
|
||||
*/
|
||||
const jsExpressionBindingPatterns = ["^v-"];
|
||||
|
||||
if (isKeyMatched(vueEventBindingPatterns)) {
|
||||
// copied from https://github.com/vuejs/vue/blob/v2.5.17/src/compiler/codegen/events.js#L3-L4
|
||||
const fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function\s*\(/;
|
||||
const simplePathRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/;
|
||||
|
||||
const value = getValue();
|
||||
return printMaybeHug(
|
||||
simplePathRE.test(value) || fnExpRE.test(value)
|
||||
? textToDoc(value, { parser: "__js_expression" })
|
||||
: stripTrailingHardline(textToDoc(value, { parser: "babylon" }))
|
||||
);
|
||||
}
|
||||
|
||||
if (isKeyMatched(vueExpressionBindingPatterns)) {
|
||||
return printMaybeHug(
|
||||
textToDoc(getValue(), { parser: "__vue_expression" })
|
||||
);
|
||||
}
|
||||
|
||||
if (isKeyMatched(jsExpressionBindingPatterns)) {
|
||||
return printMaybeHug(
|
||||
textToDoc(getValue(), { parser: "__js_expression" })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.parser === "angular") {
|
||||
const ngTextToDoc = (code, opts) =>
|
||||
// angular does not allow trailing comma
|
||||
textToDoc(code, Object.assign({ trailingComma: "none" }, opts));
|
||||
|
||||
/**
|
||||
* *directive="angularDirective"
|
||||
*/
|
||||
const ngDirectiveBindingPatterns = ["^\\*"];
|
||||
/**
|
||||
* (click)="angularStatement"
|
||||
* on-click="angularStatement"
|
||||
*/
|
||||
const ngStatementBindingPatterns = ["^\\(.+\\)$", "^on-"];
|
||||
/**
|
||||
* [target]="angularExpression"
|
||||
* bind-target="angularExpression"
|
||||
* [(target)]="angularExpression"
|
||||
* bindon-target="angularExpression"
|
||||
*/
|
||||
const ngExpressionBindingPatterns = ["^\\[.+\\]$", "^bind(on)?-"];
|
||||
|
||||
if (isKeyMatched(ngStatementBindingPatterns)) {
|
||||
return printMaybeHug(ngTextToDoc(getValue(), { parser: "__ng_action" }));
|
||||
}
|
||||
|
||||
if (isKeyMatched(ngExpressionBindingPatterns)) {
|
||||
return printMaybeHug(ngTextToDoc(getValue(), { parser: "__ng_binding" }));
|
||||
}
|
||||
|
||||
if (isKeyMatched(ngDirectiveBindingPatterns)) {
|
||||
return printMaybeHug(
|
||||
ngTextToDoc(getValue(), { parser: "__ng_directive" })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
preprocess,
|
||||
print: genericPrint,
|
||||
insertPragma,
|
||||
massageAstNode: clean,
|
||||
embed
|
||||
};
|
|
@ -1,584 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
const clean = require("./clean");
|
||||
const {
|
||||
builders,
|
||||
utils: { removeLines, stripTrailingHardline }
|
||||
} = require("../doc");
|
||||
const {
|
||||
breakParent,
|
||||
group,
|
||||
hardline,
|
||||
indent,
|
||||
join,
|
||||
line,
|
||||
literalline,
|
||||
markAsRoot,
|
||||
softline
|
||||
} = builders;
|
||||
const { hasNewlineInRange } = require("../common/util");
|
||||
const {
|
||||
normalizeParts,
|
||||
dedentString,
|
||||
forceBreakChildren,
|
||||
forceBreakContent,
|
||||
forceNextEmptyLine,
|
||||
getCommentData,
|
||||
getLastDescendant,
|
||||
hasPrettierIgnore,
|
||||
inferScriptParser,
|
||||
isScriptLikeTag,
|
||||
preferHardlineAsLeadingSpaces,
|
||||
replaceDocNewlines,
|
||||
replaceNewlines
|
||||
} = require("./utils");
|
||||
const preprocess = require("./preprocess");
|
||||
const assert = require("assert");
|
||||
|
||||
function concat(parts) {
|
||||
const newParts = normalizeParts(parts);
|
||||
return newParts.length === 0
|
||||
? ""
|
||||
: newParts.length === 1
|
||||
? newParts[0]
|
||||
: builders.concat(newParts);
|
||||
}
|
||||
|
||||
function fill(parts) {
|
||||
const newParts = [];
|
||||
|
||||
let hasSeparator = true;
|
||||
for (const part of normalizeParts(parts)) {
|
||||
switch (part) {
|
||||
case line:
|
||||
case hardline:
|
||||
case literalline:
|
||||
case softline:
|
||||
newParts.push(part);
|
||||
hasSeparator = true;
|
||||
break;
|
||||
default:
|
||||
if (!hasSeparator) {
|
||||
// `fill` needs a separator between each two parts
|
||||
newParts.push("");
|
||||
}
|
||||
newParts.push(part);
|
||||
hasSeparator = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return builders.fill(newParts);
|
||||
}
|
||||
|
||||
function embed(path, print, textToDoc /*, options */) {
|
||||
const node = path.getValue();
|
||||
switch (node.type) {
|
||||
case "text": {
|
||||
if (isScriptLikeTag(node.parent)) {
|
||||
const parser = inferScriptParser(node.parent);
|
||||
if (parser) {
|
||||
return builders.concat([
|
||||
concat([
|
||||
breakParent,
|
||||
printOpeningTagPrefix(node),
|
||||
markAsRoot(
|
||||
stripTrailingHardline(textToDoc(node.data, { parser }))
|
||||
),
|
||||
printClosingTagSuffix(node)
|
||||
])
|
||||
]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "attribute": {
|
||||
/*
|
||||
* Vue binding syntax: JS expressions
|
||||
* :class="{ 'some-key': value }"
|
||||
* v-bind:id="'list-' + id"
|
||||
* v-if="foo && !bar"
|
||||
* @click="someFunction()"
|
||||
*/
|
||||
if (/(^@)|(^v-)|:/.test(node.key) && !/^\w+$/.test(node.value)) {
|
||||
const doc = textToDoc(node.value, {
|
||||
parser: "__js_expression",
|
||||
// Use singleQuote since HTML attributes use double-quotes.
|
||||
// TODO(azz): We still need to do an entity escape on the attribute.
|
||||
singleQuote: true
|
||||
});
|
||||
return concat([
|
||||
node.key,
|
||||
'="',
|
||||
hasNewlineInRange(node.value, 0, node.value.length)
|
||||
? doc
|
||||
: removeLines(doc),
|
||||
'"'
|
||||
]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "yaml":
|
||||
return markAsRoot(
|
||||
concat([
|
||||
"---",
|
||||
hardline,
|
||||
node.value.trim().length === 0
|
||||
? ""
|
||||
: replaceDocNewlines(
|
||||
textToDoc(node.value, { parser: "yaml" }),
|
||||
literalline
|
||||
),
|
||||
"---"
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function genericPrint(path, options, print) {
|
||||
const node = path.getValue();
|
||||
switch (node.type) {
|
||||
case "root":
|
||||
return concat([group(printChildren(path, options, print)), hardline]);
|
||||
case "tag":
|
||||
case "ieConditionalComment":
|
||||
return concat([
|
||||
group(
|
||||
concat([
|
||||
printOpeningTag(path, options, print),
|
||||
node.children.length === 0
|
||||
? node.hasDanglingSpaces && node.isDanglingSpaceSensitive
|
||||
? line
|
||||
: ""
|
||||
: concat([
|
||||
forceBreakContent(node) ? breakParent : "",
|
||||
indent(
|
||||
concat([
|
||||
node.firstChild.type === "text" &&
|
||||
node.firstChild.isWhiteSpaceSensitive &&
|
||||
node.firstChild.isIndentationSensitive
|
||||
? literalline
|
||||
: node.firstChild.hasLeadingSpaces &&
|
||||
node.firstChild.isLeadingSpaceSensitive
|
||||
? line
|
||||
: softline,
|
||||
printChildren(path, options, print)
|
||||
])
|
||||
),
|
||||
(node.next
|
||||
? needsToBorrowPrevClosingTagEndMarker(node.next)
|
||||
: needsToBorrowLastChildClosingTagEndMarker(node.parent))
|
||||
? ""
|
||||
: node.lastChild.hasTrailingSpaces &&
|
||||
node.lastChild.isTrailingSpaceSensitive
|
||||
? line
|
||||
: softline
|
||||
])
|
||||
])
|
||||
),
|
||||
printClosingTag(node)
|
||||
]);
|
||||
case "text":
|
||||
return fill(
|
||||
[].concat(
|
||||
printOpeningTagPrefix(node),
|
||||
node.isWhiteSpaceSensitive
|
||||
? node.isIndentationSensitive
|
||||
? replaceNewlines(
|
||||
node.data.replace(/^\s*?\n|\n\s*?$/g, ""),
|
||||
literalline
|
||||
)
|
||||
: replaceNewlines(
|
||||
dedentString(node.data.replace(/^\s*?\n|\n\s*?$/g, "")),
|
||||
hardline
|
||||
)
|
||||
: join(line, node.data.split(/\s+/)).parts,
|
||||
printClosingTagSuffix(node)
|
||||
)
|
||||
);
|
||||
case "comment":
|
||||
case "directive": {
|
||||
const data = getCommentData(node);
|
||||
return concat([
|
||||
group(
|
||||
concat([
|
||||
printOpeningTagStart(node),
|
||||
data.trim().length === 0
|
||||
? ""
|
||||
: concat([
|
||||
indent(
|
||||
concat([
|
||||
node.prev &&
|
||||
needsToBorrowNextOpeningTagStartMarker(node.prev)
|
||||
? breakParent
|
||||
: "",
|
||||
node.type === "directive" ? " " : line,
|
||||
concat(replaceNewlines(data, hardline))
|
||||
])
|
||||
),
|
||||
node.type === "directive"
|
||||
? ""
|
||||
: (node.next
|
||||
? needsToBorrowPrevClosingTagEndMarker(node.next)
|
||||
: needsToBorrowLastChildClosingTagEndMarker(node.parent))
|
||||
? " "
|
||||
: line
|
||||
])
|
||||
])
|
||||
),
|
||||
printClosingTagEnd(node)
|
||||
]);
|
||||
}
|
||||
case "attribute":
|
||||
return concat([
|
||||
node.key,
|
||||
node.value === null
|
||||
? ""
|
||||
: concat([
|
||||
'="',
|
||||
concat(
|
||||
replaceNewlines(node.value.replace(/"/g, """), literalline)
|
||||
),
|
||||
'"'
|
||||
])
|
||||
]);
|
||||
case "yaml":
|
||||
case "toml":
|
||||
return node.raw;
|
||||
default:
|
||||
throw new Error(`Unexpected node type ${node.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
function printChildren(path, options, print) {
|
||||
const node = path.getValue();
|
||||
|
||||
if (forceBreakChildren(node)) {
|
||||
return concat([
|
||||
breakParent,
|
||||
concat(
|
||||
path.map(childPath => {
|
||||
const childNode = childPath.getValue();
|
||||
const prevBetweenLine = !childNode.prev
|
||||
? ""
|
||||
: printBetweenLine(childNode.prev, childNode);
|
||||
return concat([
|
||||
!prevBetweenLine
|
||||
? ""
|
||||
: concat([
|
||||
prevBetweenLine,
|
||||
forceNextEmptyLine(childNode.prev) ||
|
||||
childNode.prev.endLocation.line + 1 <
|
||||
childNode.startLocation.line
|
||||
? hardline
|
||||
: ""
|
||||
]),
|
||||
print(childPath)
|
||||
]);
|
||||
}, "children")
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
const parts = [];
|
||||
|
||||
path.map((childPath, childIndex) => {
|
||||
const childNode = childPath.getValue();
|
||||
|
||||
if (childIndex !== 0) {
|
||||
const prevBetweenLine = printBetweenLine(childNode.prev, childNode);
|
||||
if (prevBetweenLine) {
|
||||
if (
|
||||
forceNextEmptyLine(childNode.prev) ||
|
||||
childNode.prev.endLocation.line + 1 < childNode.startLocation.line
|
||||
) {
|
||||
parts.push(hardline, hardline);
|
||||
} else {
|
||||
parts.push(prevBetweenLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Array.prototype.push.apply(
|
||||
parts,
|
||||
childNode.type === "text" ? print(childPath).parts : [print(childPath)]
|
||||
);
|
||||
}, "children");
|
||||
|
||||
return fill(parts);
|
||||
|
||||
function printBetweenLine(prevNode, nextNode) {
|
||||
return (needsToBorrowNextOpeningTagStartMarker(prevNode) &&
|
||||
/**
|
||||
* 123<a
|
||||
* ~
|
||||
* ><b>
|
||||
*/
|
||||
(nextNode.firstChild ||
|
||||
/**
|
||||
* 123<br />
|
||||
* ~
|
||||
*/
|
||||
(nextNode.type === "tag" &&
|
||||
nextNode.isSelfClosing &&
|
||||
nextNode.attributes.length === 0))) ||
|
||||
/**
|
||||
* <img
|
||||
* src="long"
|
||||
* ~
|
||||
* />123
|
||||
*/
|
||||
(prevNode.type === "tag" &&
|
||||
prevNode.isSelfClosing &&
|
||||
needsToBorrowPrevClosingTagEndMarker(nextNode))
|
||||
? ""
|
||||
: !nextNode.isLeadingSpaceSensitive ||
|
||||
preferHardlineAsLeadingSpaces(nextNode) ||
|
||||
/**
|
||||
* Want to write us a letter? Use our<a
|
||||
* ><b><a>mailing address</a></b></a
|
||||
* ~
|
||||
* >.
|
||||
*/
|
||||
(needsToBorrowPrevClosingTagEndMarker(nextNode) &&
|
||||
prevNode.lastChild &&
|
||||
needsToBorrowParentClosingTagStartMarker(prevNode.lastChild) &&
|
||||
prevNode.lastChild.lastChild &&
|
||||
needsToBorrowParentClosingTagStartMarker(
|
||||
prevNode.lastChild.lastChild
|
||||
))
|
||||
? hardline
|
||||
: nextNode.hasLeadingSpaces
|
||||
? line
|
||||
: softline;
|
||||
}
|
||||
}
|
||||
|
||||
function printOpeningTag(path, options, print) {
|
||||
const node = path.getValue();
|
||||
return concat([
|
||||
printOpeningTagStart(node),
|
||||
!node.attributes || node.attributes.length === 0
|
||||
? node.isSelfClosing
|
||||
? /**
|
||||
* <br />
|
||||
* ^
|
||||
*/
|
||||
" "
|
||||
: ""
|
||||
: group(
|
||||
concat([
|
||||
node.prev && needsToBorrowNextOpeningTagStartMarker(node.prev)
|
||||
? /**
|
||||
* 123<a
|
||||
* attr
|
||||
* >
|
||||
*/
|
||||
breakParent
|
||||
: "",
|
||||
indent(concat([line, join(line, path.map(print, "attributes"))])),
|
||||
node.firstChild &&
|
||||
needsToBorrowParentOpeningTagEndMarker(node.firstChild)
|
||||
? /**
|
||||
* 123<a
|
||||
* attr
|
||||
* ~
|
||||
* >456
|
||||
*/
|
||||
""
|
||||
: node.isSelfClosing
|
||||
? line
|
||||
: softline
|
||||
])
|
||||
),
|
||||
node.isSelfClosing ? "" : printOpeningTagEnd(node)
|
||||
]);
|
||||
}
|
||||
|
||||
function printOpeningTagStart(node) {
|
||||
return node.prev && needsToBorrowNextOpeningTagStartMarker(node.prev)
|
||||
? ""
|
||||
: concat([printOpeningTagPrefix(node), printOpeningTagStartMarker(node)]);
|
||||
}
|
||||
|
||||
function printOpeningTagEnd(node) {
|
||||
return node.firstChild &&
|
||||
needsToBorrowParentOpeningTagEndMarker(node.firstChild)
|
||||
? ""
|
||||
: printOpeningTagEndMarker(node);
|
||||
}
|
||||
|
||||
function printClosingTag(node) {
|
||||
return concat([
|
||||
node.isSelfClosing ? "" : printClosingTagStart(node),
|
||||
printClosingTagEnd(node)
|
||||
]);
|
||||
}
|
||||
|
||||
function printClosingTagStart(node) {
|
||||
return node.lastChild &&
|
||||
needsToBorrowParentClosingTagStartMarker(node.lastChild)
|
||||
? ""
|
||||
: concat([printClosingTagPrefix(node), printClosingTagStartMarker(node)]);
|
||||
}
|
||||
|
||||
function printClosingTagEnd(node) {
|
||||
return (node.next
|
||||
? needsToBorrowPrevClosingTagEndMarker(node.next)
|
||||
: needsToBorrowLastChildClosingTagEndMarker(node.parent))
|
||||
? ""
|
||||
: concat([printClosingTagEndMarker(node), printClosingTagSuffix(node)]);
|
||||
}
|
||||
|
||||
function needsToBorrowNextOpeningTagStartMarker(node) {
|
||||
/**
|
||||
* 123<p
|
||||
* ^^
|
||||
* >
|
||||
*/
|
||||
return (
|
||||
node.next &&
|
||||
node.type === "text" &&
|
||||
node.isTrailingSpaceSensitive &&
|
||||
!node.hasTrailingSpaces
|
||||
);
|
||||
}
|
||||
|
||||
function needsToBorrowParentOpeningTagEndMarker(node) {
|
||||
/**
|
||||
* <p
|
||||
* >123
|
||||
* ^
|
||||
*
|
||||
* <p
|
||||
* ><a
|
||||
* ^
|
||||
*/
|
||||
return !node.prev && node.isLeadingSpaceSensitive && !node.hasLeadingSpaces;
|
||||
}
|
||||
|
||||
function needsToBorrowPrevClosingTagEndMarker(node) {
|
||||
/**
|
||||
* <p></p
|
||||
* >123
|
||||
* ^
|
||||
*
|
||||
* <p></p
|
||||
* ><a
|
||||
* ^
|
||||
*/
|
||||
return node.prev && node.isLeadingSpaceSensitive && !node.hasLeadingSpaces;
|
||||
}
|
||||
|
||||
function needsToBorrowLastChildClosingTagEndMarker(node) {
|
||||
/**
|
||||
* <p
|
||||
* ><a></a
|
||||
* ></p
|
||||
* ^
|
||||
* >
|
||||
*/
|
||||
return (
|
||||
node.lastChild &&
|
||||
node.lastChild.isTrailingSpaceSensitive &&
|
||||
!node.lastChild.hasTrailingSpaces &&
|
||||
getLastDescendant(node.lastChild).type !== "text"
|
||||
);
|
||||
}
|
||||
|
||||
function needsToBorrowParentClosingTagStartMarker(node) {
|
||||
/**
|
||||
* <p>
|
||||
* 123</p
|
||||
* ^^^
|
||||
* >
|
||||
*
|
||||
* 123</b
|
||||
* ></a
|
||||
* ^^^
|
||||
* >
|
||||
*/
|
||||
return (
|
||||
!node.next &&
|
||||
!node.hasTrailingSpaces &&
|
||||
node.isTrailingSpaceSensitive &&
|
||||
getLastDescendant(node).type === "text"
|
||||
);
|
||||
}
|
||||
|
||||
function printOpeningTagPrefix(node) {
|
||||
return needsToBorrowParentOpeningTagEndMarker(node)
|
||||
? printOpeningTagEndMarker(node.parent)
|
||||
: needsToBorrowPrevClosingTagEndMarker(node)
|
||||
? printClosingTagEndMarker(node.prev)
|
||||
: "";
|
||||
}
|
||||
|
||||
function printClosingTagPrefix(node) {
|
||||
return needsToBorrowLastChildClosingTagEndMarker(node)
|
||||
? printClosingTagEndMarker(node.lastChild)
|
||||
: "";
|
||||
}
|
||||
|
||||
function printClosingTagSuffix(node) {
|
||||
return needsToBorrowParentClosingTagStartMarker(node)
|
||||
? printClosingTagStartMarker(node.parent)
|
||||
: needsToBorrowNextOpeningTagStartMarker(node)
|
||||
? printOpeningTagStartMarker(node.next)
|
||||
: "";
|
||||
}
|
||||
|
||||
function printOpeningTagStartMarker(node) {
|
||||
switch (node.type) {
|
||||
case "comment":
|
||||
return "<!--";
|
||||
case "ieConditionalComment":
|
||||
return `<!--[if ${node.condition}`;
|
||||
default:
|
||||
return `<${node.name}`;
|
||||
}
|
||||
}
|
||||
|
||||
function printOpeningTagEndMarker(node) {
|
||||
assert(!node.isSelfClosing);
|
||||
switch (node.type) {
|
||||
case "ieConditionalComment":
|
||||
return "]>";
|
||||
default:
|
||||
return `>`;
|
||||
}
|
||||
}
|
||||
|
||||
function printClosingTagStartMarker(node) {
|
||||
assert(!node.isSelfClosing);
|
||||
switch (node.type) {
|
||||
case "ieConditionalComment":
|
||||
return "<!";
|
||||
default:
|
||||
return `</${node.name}`;
|
||||
}
|
||||
}
|
||||
|
||||
function printClosingTagEndMarker(node) {
|
||||
switch (node.type) {
|
||||
case "comment":
|
||||
return "-->";
|
||||
case "ieConditionalComment":
|
||||
return `[endif]-->`;
|
||||
case "tag":
|
||||
if (node.isSelfClosing) {
|
||||
return "/>";
|
||||
}
|
||||
// fall through
|
||||
default:
|
||||
return ">";
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
preprocess,
|
||||
print: genericPrint,
|
||||
massageAstNode: clean,
|
||||
embed,
|
||||
hasPrettierIgnore
|
||||
};
|
|
@ -0,0 +1,64 @@
|
|||
"use strict";
|
||||
|
||||
const {
|
||||
builders: { concat, ifBreak, join, line }
|
||||
} = require("../doc");
|
||||
const parseSrcset = require("parse-srcset");
|
||||
|
||||
function printImgSrcset(value) {
|
||||
const srcset = parseSrcset(value, {
|
||||
logger: {
|
||||
error(message) {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const hasW = srcset.some(src => src.w);
|
||||
const hasH = srcset.some(src => src.h);
|
||||
const hasX = srcset.some(src => src.d);
|
||||
|
||||
if (hasW + hasH + hasX !== 1) {
|
||||
throw new Error(`Mixed descriptor in srcset is not supported`);
|
||||
}
|
||||
|
||||
const key = hasW ? "w" : hasH ? "h" : "d";
|
||||
const unit = hasW ? "w" : hasH ? "h" : "x";
|
||||
|
||||
const getMax = values => Math.max.apply(Math, values);
|
||||
|
||||
const urls = srcset.map(src => src.url);
|
||||
const maxUrlLength = getMax(urls.map(url => url.length));
|
||||
|
||||
const descriptors = srcset
|
||||
.map(src => src[key])
|
||||
.map(descriptor => (descriptor ? descriptor.toString() : ""));
|
||||
const descriptorLeftLengths = descriptors.map(descriptor => {
|
||||
const index = descriptor.indexOf(".");
|
||||
return index === -1 ? descriptor.length : index;
|
||||
});
|
||||
const maxDescriptorLeftLength = getMax(descriptorLeftLengths);
|
||||
|
||||
return join(
|
||||
concat([",", line]),
|
||||
urls.map((url, index) => {
|
||||
const parts = [url];
|
||||
|
||||
const descriptor = descriptors[index];
|
||||
if (descriptor) {
|
||||
const urlPadding = maxUrlLength - url.length + 1;
|
||||
const descriptorPadding =
|
||||
maxDescriptorLeftLength - descriptorLeftLengths[index];
|
||||
|
||||
const alignment = " ".repeat(urlPadding + descriptorPadding);
|
||||
parts.push(ifBreak(alignment, " "), descriptor + unit);
|
||||
}
|
||||
|
||||
return concat(parts);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
printImgSrcset
|
||||
};
|
|
@ -0,0 +1,72 @@
|
|||
"use strict";
|
||||
|
||||
const {
|
||||
builders: { concat, group }
|
||||
} = require("../doc");
|
||||
|
||||
/**
|
||||
* v-for="... in ..."
|
||||
* v-for="... of ..."
|
||||
* v-for="(..., ...) in ..."
|
||||
* v-for="(..., ...) of ..."
|
||||
*/
|
||||
function printVueFor(value, textToDoc) {
|
||||
const { left, operator, right } = parseVueFor(value);
|
||||
return concat([
|
||||
group(
|
||||
textToDoc(`function _(${left}) {}`, {
|
||||
parser: "babylon",
|
||||
__isVueForBindingLeft: true
|
||||
})
|
||||
),
|
||||
" ",
|
||||
operator,
|
||||
" ",
|
||||
textToDoc(right, { parser: "__js_expression" })
|
||||
]);
|
||||
}
|
||||
|
||||
// modified from https://github.com/vuejs/vue/blob/v2.5.17/src/compiler/parser/index.js#L370-L387
|
||||
function parseVueFor(value) {
|
||||
const forAliasRE = /([^]*?)\s+(in|of)\s+([^]*)/;
|
||||
const forIteratorRE = /,([^,}\]]*)(?:,([^,}\]]*))?$/;
|
||||
const stripParensRE = /^\(|\)$/g;
|
||||
|
||||
const inMatch = value.match(forAliasRE);
|
||||
if (!inMatch) {
|
||||
return;
|
||||
}
|
||||
const res = {};
|
||||
res.for = inMatch[3].trim();
|
||||
const alias = inMatch[1].trim().replace(stripParensRE, "");
|
||||
const iteratorMatch = alias.match(forIteratorRE);
|
||||
if (iteratorMatch) {
|
||||
res.alias = alias.replace(forIteratorRE, "");
|
||||
res.iterator1 = iteratorMatch[1].trim();
|
||||
if (iteratorMatch[2]) {
|
||||
res.iterator2 = iteratorMatch[2].trim();
|
||||
}
|
||||
} else {
|
||||
res.alias = alias;
|
||||
}
|
||||
|
||||
return {
|
||||
left: `${[res.alias, res.iterator1, res.iterator2]
|
||||
.filter(Boolean)
|
||||
.join(",")}`,
|
||||
operator: inMatch[2],
|
||||
right: res.for
|
||||
};
|
||||
}
|
||||
|
||||
function printVueSlotScope(value, textToDoc) {
|
||||
return textToDoc(`function _(${value}) {}`, {
|
||||
parser: "babylon",
|
||||
__isVueSlotScope: true
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
printVueFor,
|
||||
printVueSlotScope
|
||||
};
|
|
@ -18,40 +18,6 @@ const htmlElementAttributes = require("html-element-attributes");
|
|||
const HTML_TAGS = arrayToMap(htmlTagNames);
|
||||
const HTML_ELEMENT_ATTRIBUTES = mapObject(htmlElementAttributes, arrayToMap);
|
||||
|
||||
// NOTE: must be same as the one in htmlparser2 so that the parsing won't be inconsistent
|
||||
// https://github.com/fb55/htmlparser2/blob/v3.9.2/lib/Parser.js#L59-L91
|
||||
const VOID_TAGS = arrayToMap([
|
||||
"area",
|
||||
"base",
|
||||
"basefont",
|
||||
"br",
|
||||
"col",
|
||||
"command",
|
||||
"embed",
|
||||
"frame",
|
||||
"hr",
|
||||
"img",
|
||||
"input",
|
||||
"isindex",
|
||||
"keygen",
|
||||
"link",
|
||||
"meta",
|
||||
"param",
|
||||
"source",
|
||||
"track",
|
||||
"wbr",
|
||||
|
||||
"path",
|
||||
"circle",
|
||||
"ellipse",
|
||||
"line",
|
||||
"rect",
|
||||
"use",
|
||||
"stop",
|
||||
"polyline",
|
||||
"polygon"
|
||||
]);
|
||||
|
||||
function arrayToMap(array) {
|
||||
const map = Object.create(null);
|
||||
for (const value of array) {
|
||||
|
@ -70,10 +36,18 @@ function mapObject(object, fn) {
|
|||
|
||||
function hasPrettierIgnore(path) {
|
||||
const node = path.getValue();
|
||||
if (node.type === "attribute") {
|
||||
if (node.type === "attribute" || node.type === "text") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: handle non-text children in <pre>
|
||||
if (
|
||||
isPreLikeNode(node) &&
|
||||
node.children.some(child => child.type !== "text")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const parentNode = path.getParentNode();
|
||||
if (!parentNode) {
|
||||
return false;
|
||||
|
@ -89,65 +63,118 @@ function hasPrettierIgnore(path) {
|
|||
}
|
||||
|
||||
function isPrettierIgnore(node) {
|
||||
return node.type === "comment" && node.data.trim() === "prettier-ignore";
|
||||
return node.type === "comment" && node.value.trim() === "prettier-ignore";
|
||||
}
|
||||
|
||||
function isTag(node) {
|
||||
return node.type === "tag";
|
||||
function getPrettierIgnoreAttributeCommentData(value) {
|
||||
const match = value.trim().match(/^prettier-ignore-attribute(?:\s+([^]+))?$/);
|
||||
|
||||
if (!match) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!match[1]) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return match[1].split(/\s+/);
|
||||
}
|
||||
|
||||
function isScriptLikeTag(node) {
|
||||
return isTag(node) && (node.name === "script" || node.name === "style");
|
||||
return (
|
||||
node.type === "element" &&
|
||||
(node.fullName === "script" ||
|
||||
node.fullName === "style" ||
|
||||
node.fullName === "svg:style")
|
||||
);
|
||||
}
|
||||
|
||||
function isFrontMatterNode(node) {
|
||||
return node.type === "yaml" || node.type === "toml";
|
||||
}
|
||||
|
||||
function isLeadingSpaceSensitiveNode(node, { prev, parent }) {
|
||||
function canHaveInterpolation(node) {
|
||||
return node.children && !isScriptLikeTag(node);
|
||||
}
|
||||
|
||||
function isWhitespaceSensitiveNode(node) {
|
||||
return (
|
||||
isScriptLikeTag(node) ||
|
||||
node.type === "interpolation" ||
|
||||
isIndentationSensitiveNode(node)
|
||||
);
|
||||
}
|
||||
|
||||
function isIndentationSensitiveNode(node) {
|
||||
return getNodeCssStyleWhiteSpace(node).startsWith("pre");
|
||||
}
|
||||
|
||||
function isLeadingSpaceSensitiveNode(node) {
|
||||
if (isFrontMatterNode(node)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!parent || parent.cssDisplay === "none") {
|
||||
if (!node.parent || node.parent.cssDisplay === "none") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
!prev &&
|
||||
(parent.type === "root" ||
|
||||
isScriptLikeTag(parent) ||
|
||||
isBlockLikeCssDisplay(parent.cssDisplay))
|
||||
!node.prev &&
|
||||
node.parent.type === "element" &&
|
||||
node.parent.tagDefinition.ignoreFirstLf
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prev && isBlockLikeCssDisplay(prev.cssDisplay)) {
|
||||
if (isPreLikeNode(node.parent)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
!node.prev &&
|
||||
(node.parent.type === "root" ||
|
||||
isScriptLikeTag(node.parent) ||
|
||||
!isFirstChildLeadingSpaceSensitiveCssDisplay(node.parent.cssDisplay))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
node.prev &&
|
||||
!isNextLeadingSpaceSensitiveCssDisplay(node.prev.cssDisplay)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function isTrailingSpaceSensitiveNode(node, { next, parent }) {
|
||||
function isTrailingSpaceSensitiveNode(node) {
|
||||
if (isFrontMatterNode(node)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!parent || parent.cssDisplay === "none") {
|
||||
if (!node.parent || node.parent.cssDisplay === "none") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isPreLikeNode(node.parent)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
!next &&
|
||||
(parent.type === "root" ||
|
||||
isScriptLikeTag(parent) ||
|
||||
isBlockLikeCssDisplay(parent.cssDisplay))
|
||||
!node.next &&
|
||||
(node.parent.type === "root" ||
|
||||
isScriptLikeTag(node.parent) ||
|
||||
!isLastChildTrailingSpaceSensitiveCssDisplay(node.parent.cssDisplay))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (next && isBlockLikeCssDisplay(next.cssDisplay)) {
|
||||
if (
|
||||
node.next &&
|
||||
!isPrevTrailingSpaceSensitiveCssDisplay(node.next.cssDisplay)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -155,36 +182,12 @@ function isTrailingSpaceSensitiveNode(node, { next, parent }) {
|
|||
}
|
||||
|
||||
function isDanglingSpaceSensitiveNode(node) {
|
||||
return !isBlockLikeCssDisplay(node.cssDisplay);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {unknown} node
|
||||
* @param {(node: unknown, stack: Array<string | object>)} fn
|
||||
* @param {unknown=} parent
|
||||
*/
|
||||
function mapNode(node, fn, stack = []) {
|
||||
const newNode = Object.assign({}, node);
|
||||
|
||||
if (newNode.children) {
|
||||
newNode.children = newNode.children.map((child, childIndex) =>
|
||||
mapNode(child, fn, [childIndex, node].concat(stack))
|
||||
return (
|
||||
isDanglingSpaceSensitiveCssDisplay(node.cssDisplay) &&
|
||||
!isScriptLikeTag(node)
|
||||
);
|
||||
}
|
||||
|
||||
return fn(newNode, stack);
|
||||
}
|
||||
|
||||
function getPrevNode(stack) {
|
||||
const [index, parent] = stack;
|
||||
|
||||
if (typeof index !== "number" || index === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return parent.children[index - 1];
|
||||
}
|
||||
|
||||
function replaceNewlines(text, replacement) {
|
||||
return text
|
||||
.split(/(\n)/g)
|
||||
|
@ -202,16 +205,20 @@ function replaceDocNewlines(doc, replacement) {
|
|||
}
|
||||
|
||||
function forceNextEmptyLine(node) {
|
||||
return isFrontMatterNode(node);
|
||||
return (
|
||||
isFrontMatterNode(node) ||
|
||||
(node.next &&
|
||||
node.sourceSpan.end.line + 1 < node.next.sourceSpan.start.line)
|
||||
);
|
||||
}
|
||||
|
||||
/** firstChild leadingSpaces and lastChild trailingSpaces */
|
||||
function forceBreakContent(node) {
|
||||
return (
|
||||
forceBreakChildren(node) ||
|
||||
(isTag(node) &&
|
||||
(node.type === "element" &&
|
||||
node.children.length !== 0 &&
|
||||
(["body", "template"].indexOf(node.name) !== -1 ||
|
||||
(["body", "template", "script", "style"].indexOf(node.name) !== -1 ||
|
||||
node.children.some(child => hasNonTextChild(child))))
|
||||
);
|
||||
}
|
||||
|
@ -219,7 +226,7 @@ function forceBreakContent(node) {
|
|||
/** spaces between children */
|
||||
function forceBreakChildren(node) {
|
||||
return (
|
||||
isTag(node) &&
|
||||
node.type === "element" &&
|
||||
node.children.length !== 0 &&
|
||||
(["html", "head", "ul", "ol", "select"].indexOf(node.name) !== -1 ||
|
||||
(node.cssDisplay.startsWith("table") && node.cssDisplay !== "table-cell"))
|
||||
|
@ -229,14 +236,48 @@ function forceBreakChildren(node) {
|
|||
function preferHardlineAsLeadingSpaces(node) {
|
||||
return (
|
||||
preferHardlineAsSurroundingSpaces(node) ||
|
||||
(node.prev && preferHardlineAsTrailingSpaces(node.prev))
|
||||
(node.prev && preferHardlineAsTrailingSpaces(node.prev)) ||
|
||||
isCustomElementWithSurroundingLineBreak(node)
|
||||
);
|
||||
}
|
||||
|
||||
function preferHardlineAsTrailingSpaces(node) {
|
||||
return (
|
||||
preferHardlineAsSurroundingSpaces(node) ||
|
||||
(isTag(node) && node.name === "br")
|
||||
(node.type === "element" && node.fullName === "br") ||
|
||||
isCustomElementWithSurroundingLineBreak(node)
|
||||
);
|
||||
}
|
||||
|
||||
function isCustomElementWithSurroundingLineBreak(node) {
|
||||
return isCustomElement(node) && hasSurroundingLineBreak(node);
|
||||
}
|
||||
|
||||
function isCustomElement(node) {
|
||||
return node.type === "element" && !node.namespace && node.name.includes("-");
|
||||
}
|
||||
|
||||
function hasSurroundingLineBreak(node) {
|
||||
return hasLeadingLineBreak(node) && hasTrailingLineBreak(node);
|
||||
}
|
||||
|
||||
function hasLeadingLineBreak(node) {
|
||||
return (
|
||||
node.hasLeadingSpaces &&
|
||||
(node.prev
|
||||
? node.prev.sourceSpan.end.line < node.sourceSpan.start.line
|
||||
: node.parent.type === "root" ||
|
||||
node.parent.startSourceSpan.end.line < node.sourceSpan.start.line)
|
||||
);
|
||||
}
|
||||
|
||||
function hasTrailingLineBreak(node) {
|
||||
return (
|
||||
node.hasTrailingSpaces &&
|
||||
(node.next
|
||||
? node.next.sourceSpan.start.line > node.sourceSpan.end.line
|
||||
: node.parent.type === "root" ||
|
||||
node.parent.endSourceSpan.start.line > node.sourceSpan.end.line)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -246,7 +287,7 @@ function preferHardlineAsSurroundingSpaces(node) {
|
|||
case "comment":
|
||||
case "directive":
|
||||
return true;
|
||||
case "tag":
|
||||
case "element":
|
||||
return ["script", "select"].indexOf(node.name) !== -1;
|
||||
}
|
||||
return false;
|
||||
|
@ -261,54 +302,127 @@ function hasNonTextChild(node) {
|
|||
}
|
||||
|
||||
function inferScriptParser(node) {
|
||||
if (node.name === "script" && !node.attrMap.src) {
|
||||
if (
|
||||
node.name === "script" &&
|
||||
((!node.attribs.lang && !node.attribs.type) ||
|
||||
node.attribs.type === "text/javascript" ||
|
||||
node.attribs.type === "text/babel" ||
|
||||
node.attribs.type === "application/javascript")
|
||||
(!node.attrMap.lang && !node.attrMap.type) ||
|
||||
node.attrMap.type === "module" ||
|
||||
node.attrMap.type === "text/javascript" ||
|
||||
node.attrMap.type === "text/babel" ||
|
||||
node.attrMap.type === "application/javascript"
|
||||
) {
|
||||
return "babylon";
|
||||
}
|
||||
|
||||
if (
|
||||
node.name === "script" &&
|
||||
(node.attribs.type === "application/x-typescript" ||
|
||||
node.attribs.lang === "ts")
|
||||
node.attrMap.type === "application/x-typescript" ||
|
||||
node.attrMap.lang === "ts" ||
|
||||
node.attrMap.lang === "tsx"
|
||||
) {
|
||||
return "typescript";
|
||||
}
|
||||
|
||||
if (node.attrMap.type === "text/markdown") {
|
||||
return "markdown";
|
||||
}
|
||||
}
|
||||
|
||||
if (node.name === "style") {
|
||||
if (!node.attrMap.lang || node.attrMap.lang === "postcss") {
|
||||
return "css";
|
||||
}
|
||||
|
||||
if (node.attrMap.lang === "scss") {
|
||||
return "scss";
|
||||
}
|
||||
|
||||
if (node.attrMap.lang === "less") {
|
||||
return "less";
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* firstChild leadingSpaces, lastChild trailingSpaces, and danglingSpaces are insensitive
|
||||
*/
|
||||
function isBlockLikeCssDisplay(cssDisplay) {
|
||||
return cssDisplay === "block" || cssDisplay.startsWith("table");
|
||||
return (
|
||||
cssDisplay === "block" ||
|
||||
cssDisplay === "list-item" ||
|
||||
cssDisplay.startsWith("table")
|
||||
);
|
||||
}
|
||||
|
||||
function getNodeCssStyleDisplay(node, prevNode, options) {
|
||||
switch (getNodeCssStyleWhiteSpace(node)) {
|
||||
case "pre":
|
||||
case "pre-wrap":
|
||||
// textarea-like
|
||||
return "block";
|
||||
function isFirstChildLeadingSpaceSensitiveCssDisplay(cssDisplay) {
|
||||
return !isBlockLikeCssDisplay(cssDisplay) && cssDisplay !== "inline-block";
|
||||
}
|
||||
|
||||
if (prevNode && prevNode.type === "comment") {
|
||||
function isLastChildTrailingSpaceSensitiveCssDisplay(cssDisplay) {
|
||||
return !isBlockLikeCssDisplay(cssDisplay) && cssDisplay !== "inline-block";
|
||||
}
|
||||
|
||||
function isPrevTrailingSpaceSensitiveCssDisplay(cssDisplay) {
|
||||
return !isBlockLikeCssDisplay(cssDisplay);
|
||||
}
|
||||
|
||||
function isNextLeadingSpaceSensitiveCssDisplay(cssDisplay) {
|
||||
return !isBlockLikeCssDisplay(cssDisplay);
|
||||
}
|
||||
|
||||
function isDanglingSpaceSensitiveCssDisplay(cssDisplay) {
|
||||
return !isBlockLikeCssDisplay(cssDisplay) && cssDisplay !== "inline-block";
|
||||
}
|
||||
|
||||
function isPreLikeNode(node) {
|
||||
return getNodeCssStyleWhiteSpace(node).startsWith("pre");
|
||||
}
|
||||
|
||||
function countParents(path, predicate = () => true) {
|
||||
let counter = 0;
|
||||
for (let i = path.stack.length - 1; i >= 0; i--) {
|
||||
const value = path.stack[i];
|
||||
if (
|
||||
value &&
|
||||
typeof value === "object" &&
|
||||
!Array.isArray(value) &&
|
||||
predicate(value)
|
||||
) {
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
return counter;
|
||||
}
|
||||
|
||||
function hasParent(node, fn) {
|
||||
let current = node;
|
||||
|
||||
while (current) {
|
||||
if (fn(current)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
current = current.parent;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getNodeCssStyleDisplay(node, options) {
|
||||
if (node.prev && node.prev.type === "comment") {
|
||||
// <!-- display: block -->
|
||||
const match = prevNode.data.match(/^\s*display:\s*([a-z]+)\s*$/);
|
||||
const match = node.prev.value.match(/^\s*display:\s*([a-z]+)\s*$/);
|
||||
if (match) {
|
||||
return match[1];
|
||||
}
|
||||
}
|
||||
|
||||
let isInSvgForeignObject = false;
|
||||
if (node.type === "element" && node.namespace === "svg") {
|
||||
if (hasParent(node, parent => parent.fullName === "svg:foreignObject")) {
|
||||
isInSvgForeignObject = true;
|
||||
} else {
|
||||
return node.name === "svg" ? "inline-block" : "block";
|
||||
}
|
||||
}
|
||||
|
||||
switch (options.htmlWhitespaceSensitivity) {
|
||||
case "strict":
|
||||
return "inline";
|
||||
|
@ -316,21 +430,27 @@ function getNodeCssStyleDisplay(node, prevNode, options) {
|
|||
return "block";
|
||||
default:
|
||||
return (
|
||||
(isTag(node) && CSS_DISPLAY_TAGS[node.name]) || CSS_DISPLAY_DEFAULT
|
||||
(node.type === "element" &&
|
||||
(!node.namespace || isInSvgForeignObject) &&
|
||||
CSS_DISPLAY_TAGS[node.name]) ||
|
||||
CSS_DISPLAY_DEFAULT
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function getNodeCssStyleWhiteSpace(node) {
|
||||
return (
|
||||
(isTag(node) && CSS_WHITE_SPACE_TAGS[node.name]) || CSS_WHITE_SPACE_DEFAULT
|
||||
(node.type === "element" &&
|
||||
!node.namespace &&
|
||||
CSS_WHITE_SPACE_TAGS[node.name]) ||
|
||||
CSS_WHITE_SPACE_DEFAULT
|
||||
);
|
||||
}
|
||||
|
||||
function getCommentData(node) {
|
||||
const rightTrimmedData = node.data.trimRight();
|
||||
const rightTrimmedValue = node.value.trimRight();
|
||||
|
||||
const hasLeadingEmptyLine = /^[^\S\n]*?\n/.test(node.data);
|
||||
const hasLeadingEmptyLine = /^[^\S\n]*?\n/.test(node.value);
|
||||
if (hasLeadingEmptyLine) {
|
||||
/**
|
||||
* <!--
|
||||
|
@ -338,7 +458,7 @@ function getCommentData(node) {
|
|||
* 456
|
||||
* -->
|
||||
*/
|
||||
return dedentString(rightTrimmedData.replace(/^\s*\n/, ""));
|
||||
return dedentString(rightTrimmedValue.replace(/^\s*\n/, ""));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -351,17 +471,19 @@ function getCommentData(node) {
|
|||
*
|
||||
* -->
|
||||
*/
|
||||
if (!rightTrimmedData.includes("\n")) {
|
||||
return rightTrimmedData.trimLeft();
|
||||
if (!rightTrimmedValue.includes("\n")) {
|
||||
return rightTrimmedValue.trimLeft();
|
||||
}
|
||||
|
||||
const firstNewlineIndex = rightTrimmedData.indexOf("\n");
|
||||
const dataWithoutLeadingLine = rightTrimmedData.slice(firstNewlineIndex + 1);
|
||||
const firstNewlineIndex = rightTrimmedValue.indexOf("\n");
|
||||
const dataWithoutLeadingLine = rightTrimmedValue.slice(firstNewlineIndex + 1);
|
||||
const minIndentationForDataWithoutLeadingLine = getMinIndentation(
|
||||
dataWithoutLeadingLine
|
||||
);
|
||||
|
||||
const commentDataStartColumn = node.startLocation.column + "<!--".length;
|
||||
const leadingSpaces = rightTrimmedValue.match(/^[^\n\S]*/)[0].length;
|
||||
const commentDataStartColumn =
|
||||
node.sourceSpan.start.col + "<!--".length + leadingSpaces;
|
||||
|
||||
/**
|
||||
* <!-- 123
|
||||
|
@ -369,17 +491,18 @@ function getCommentData(node) {
|
|||
*/
|
||||
if (minIndentationForDataWithoutLeadingLine >= commentDataStartColumn) {
|
||||
return dedentString(
|
||||
" ".repeat(commentDataStartColumn) + "\n" + rightTrimmedData
|
||||
" ".repeat(commentDataStartColumn) +
|
||||
rightTrimmedValue.slice(leadingSpaces)
|
||||
);
|
||||
}
|
||||
|
||||
const leadingLineData = rightTrimmedData.slice(0, firstNewlineIndex);
|
||||
const leadingLineValue = rightTrimmedValue.slice(0, firstNewlineIndex);
|
||||
/**
|
||||
* <!-- 123
|
||||
* 456 -->
|
||||
*/
|
||||
return (
|
||||
leadingLineData.trim() +
|
||||
leadingLineValue.trim() +
|
||||
"\n" +
|
||||
dedentString(
|
||||
dataWithoutLeadingLine,
|
||||
|
@ -392,6 +515,10 @@ function getMinIndentation(text) {
|
|||
let minIndentation = Infinity;
|
||||
|
||||
for (const lineText of text.split("\n")) {
|
||||
if (lineText.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (/\S/.test(lineText[0])) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -422,11 +549,19 @@ function dedentString(text, minIndent = getMinIndentation(text)) {
|
|||
function normalizeParts(parts) {
|
||||
const newParts = [];
|
||||
|
||||
for (const part of parts) {
|
||||
const restParts = parts.slice();
|
||||
while (restParts.length !== 0) {
|
||||
const part = restParts.shift();
|
||||
|
||||
if (!part) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (part.type === "concat") {
|
||||
Array.prototype.unshift.apply(restParts, part.parts);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
newParts.length !== 0 &&
|
||||
typeof newParts[newParts.length - 1] === "string" &&
|
||||
|
@ -442,10 +577,15 @@ function normalizeParts(parts) {
|
|||
return newParts;
|
||||
}
|
||||
|
||||
function identity(x) {
|
||||
return x;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
HTML_ELEMENT_ATTRIBUTES,
|
||||
HTML_TAGS,
|
||||
VOID_TAGS,
|
||||
canHaveInterpolation,
|
||||
countParents,
|
||||
dedentString,
|
||||
forceBreakChildren,
|
||||
forceBreakContent,
|
||||
|
@ -454,15 +594,18 @@ module.exports = {
|
|||
getLastDescendant,
|
||||
getNodeCssStyleDisplay,
|
||||
getNodeCssStyleWhiteSpace,
|
||||
getPrevNode,
|
||||
getPrettierIgnoreAttributeCommentData,
|
||||
hasPrettierIgnore,
|
||||
identity,
|
||||
inferScriptParser,
|
||||
isDanglingSpaceSensitiveNode,
|
||||
isFrontMatterNode,
|
||||
isIndentationSensitiveNode,
|
||||
isLeadingSpaceSensitiveNode,
|
||||
isPreLikeNode,
|
||||
isScriptLikeTag,
|
||||
isTrailingSpaceSensitiveNode,
|
||||
mapNode,
|
||||
isWhitespaceSensitiveNode,
|
||||
normalizeParts,
|
||||
preferHardlineAsLeadingSpaces,
|
||||
preferHardlineAsTrailingSpaces,
|
||||
|
|
|
@ -131,21 +131,33 @@ function clean(ast, newObj, parent) {
|
|||
newObj.value.expression.quasis.forEach(q => delete q.value);
|
||||
}
|
||||
|
||||
// CSS template literals in Angular Component decorator
|
||||
// Angular Components: Inline HTML template and Inline CSS styles
|
||||
const expression = ast.expression || ast.callee;
|
||||
if (
|
||||
ast.type === "Decorator" &&
|
||||
expression.type === "CallExpression" &&
|
||||
expression.callee.name === "Component" &&
|
||||
expression.arguments.length === 1 &&
|
||||
expression.arguments[0].properties.some(
|
||||
prop =>
|
||||
prop.key.name === "styles" && prop.value.type === "ArrayExpression"
|
||||
)
|
||||
expression.arguments.length === 1
|
||||
) {
|
||||
newObj.expression.arguments[0].properties.forEach(prop => {
|
||||
const astProps = ast.expression.arguments[0].properties;
|
||||
newObj.expression.arguments[0].properties.forEach((prop, index) => {
|
||||
let templateLiteral = null;
|
||||
|
||||
switch (astProps[index].key.name) {
|
||||
case "styles":
|
||||
if (prop.value.type === "ArrayExpression") {
|
||||
prop.value.elements[0].quasis.forEach(q => delete q.value);
|
||||
templateLiteral = prop.value.elements[0];
|
||||
}
|
||||
break;
|
||||
case "template":
|
||||
if (prop.value.type === "TemplateLiteral") {
|
||||
templateLiteral = prop.value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (templateLiteral) {
|
||||
templateLiteral.quasis.forEach(q => delete q.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -159,7 +171,8 @@ function clean(ast, newObj, parent) {
|
|||
ast.tag.name === "graphql" ||
|
||||
ast.tag.name === "css" ||
|
||||
ast.tag.name === "md" ||
|
||||
ast.tag.name === "markdown")) ||
|
||||
ast.tag.name === "markdown" ||
|
||||
ast.tag.name === "html")) ||
|
||||
ast.tag.type === "CallExpression")
|
||||
) {
|
||||
newObj.quasi.quasis.forEach(quasi => delete quasi.value);
|
||||
|
@ -170,14 +183,17 @@ function clean(ast, newObj, parent) {
|
|||
// we will not trim the comment value and we will expect exactly one space on
|
||||
// either side of the GraphQL string
|
||||
// Also see ./embed.js
|
||||
const hasGraphQLComment =
|
||||
const hasLanguageComment =
|
||||
ast.leadingComments &&
|
||||
ast.leadingComments.some(
|
||||
comment =>
|
||||
comment.type === "CommentBlock" && comment.value === " GraphQL "
|
||||
comment.type === "CommentBlock" &&
|
||||
["GraphQL", "HTML"].some(
|
||||
languageName => comment.value === ` ${languageName} `
|
||||
)
|
||||
);
|
||||
if (
|
||||
hasGraphQLComment ||
|
||||
hasLanguageComment ||
|
||||
(parent.type === "CallExpression" && parent.callee.name === "graphql")
|
||||
) {
|
||||
newObj.quasis.forEach(quasi => delete quasi.value);
|
||||
|
|
|
@ -8,6 +8,7 @@ const {
|
|||
softline,
|
||||
literalline,
|
||||
concat,
|
||||
group,
|
||||
dedentToRoot
|
||||
},
|
||||
utils: { mapDoc, stripTrailingHardline }
|
||||
|
@ -132,6 +133,14 @@ function embed(path, print, textToDoc /*, options */) {
|
|||
]);
|
||||
}
|
||||
|
||||
if (isHtml(path)) {
|
||||
return printHtmlTemplateLiteral(path, print, textToDoc, "html");
|
||||
}
|
||||
|
||||
if (isAngularComponentTemplate(path)) {
|
||||
return printHtmlTemplateLiteral(path, print, textToDoc, "angular");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -179,18 +188,6 @@ function embed(path, print, textToDoc /*, options */) {
|
|||
}
|
||||
}
|
||||
|
||||
function isPropertyWithinAngularComponentDecorator(path, parentIndexToCheck) {
|
||||
const parent = path.getParentNode(parentIndexToCheck);
|
||||
return !!(
|
||||
parent &&
|
||||
parent.type === "Decorator" &&
|
||||
parent.expression &&
|
||||
parent.expression.type === "CallExpression" &&
|
||||
parent.expression.callee &&
|
||||
parent.expression.callee.name === "Component"
|
||||
);
|
||||
}
|
||||
|
||||
function getIndentation(str) {
|
||||
const firstMatchedIndent = str.match(/^([^\S\n]*)\S/m);
|
||||
return firstMatchedIndent === null ? "" : firstMatchedIndent[1];
|
||||
|
@ -361,9 +358,6 @@ function isStyledJsx(path) {
|
|||
* ...which are both within template literals somewhere
|
||||
* inside of the Component decorator factory.
|
||||
*
|
||||
* TODO: Format HTML template once prettier's HTML
|
||||
* formatting is "ready"
|
||||
*
|
||||
* E.g.
|
||||
* @Component({
|
||||
* template: `<div>...</div>`,
|
||||
|
@ -371,21 +365,42 @@ function isStyledJsx(path) {
|
|||
* })
|
||||
*/
|
||||
function isAngularComponentStyles(path) {
|
||||
const parent = path.getParentNode();
|
||||
const parentParent = path.getParentNode(1);
|
||||
const isWithinArrayValueFromProperty = !!(
|
||||
parent &&
|
||||
(parent.type === "ArrayExpression" && parentParent.type === "Property")
|
||||
return isPathMatch(
|
||||
path,
|
||||
[
|
||||
node => node.type === "TemplateLiteral",
|
||||
(node, name) => node.type === "ArrayExpression" && name === "elements",
|
||||
(node, name) =>
|
||||
node.type === "Property" &&
|
||||
node.key.type === "Identifier" &&
|
||||
node.key.name === "styles" &&
|
||||
name === "value"
|
||||
].concat(getAngularComponentObjectExpressionPredicates())
|
||||
);
|
||||
if (
|
||||
isWithinArrayValueFromProperty &&
|
||||
isPropertyWithinAngularComponentDecorator(path, 4)
|
||||
) {
|
||||
if (parentParent.key && parentParent.key.name === "styles") {
|
||||
return true;
|
||||
}
|
||||
function isAngularComponentTemplate(path) {
|
||||
return isPathMatch(
|
||||
path,
|
||||
[
|
||||
node => node.type === "TemplateLiteral",
|
||||
(node, name) =>
|
||||
node.type === "Property" &&
|
||||
node.key.type === "Identifier" &&
|
||||
node.key.name === "template" &&
|
||||
name === "value"
|
||||
].concat(getAngularComponentObjectExpressionPredicates())
|
||||
);
|
||||
}
|
||||
return false;
|
||||
function getAngularComponentObjectExpressionPredicates() {
|
||||
return [
|
||||
(node, name) => node.type === "ObjectExpression" && name === "properties",
|
||||
(node, name) =>
|
||||
node.type === "CallExpression" &&
|
||||
node.callee.type === "Identifier" &&
|
||||
node.callee.name === "Component" &&
|
||||
name === "arguments",
|
||||
(node, name) => node.type === "Decorator" && name === "expression"
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -470,20 +485,8 @@ function isGraphQL(path) {
|
|||
const node = path.getValue();
|
||||
const parent = path.getParentNode();
|
||||
|
||||
// This checks for a leading comment that is exactly `/* GraphQL */`
|
||||
// In order to be in line with other implementations of this comment tag
|
||||
// we will not trim the comment value and we will expect exactly one space on
|
||||
// either side of the GraphQL string
|
||||
// Also see ./clean.js
|
||||
const hasGraphQLComment =
|
||||
node.leadingComments &&
|
||||
node.leadingComments.some(
|
||||
comment =>
|
||||
comment.type === "CommentBlock" && comment.value === " GraphQL "
|
||||
);
|
||||
|
||||
return (
|
||||
hasGraphQLComment ||
|
||||
hasLanguageComment(node, "GraphQL") ||
|
||||
(parent &&
|
||||
((parent.type === "TaggedTemplateExpression" &&
|
||||
((parent.tag.type === "MemberExpression" &&
|
||||
|
@ -497,4 +500,133 @@ function isGraphQL(path) {
|
|||
);
|
||||
}
|
||||
|
||||
function hasLanguageComment(node, languageName) {
|
||||
// This checks for a leading comment that is exactly `/* GraphQL */`
|
||||
// In order to be in line with other implementations of this comment tag
|
||||
// we will not trim the comment value and we will expect exactly one space on
|
||||
// either side of the GraphQL string
|
||||
// Also see ./clean.js
|
||||
return (
|
||||
node.leadingComments &&
|
||||
node.leadingComments.some(
|
||||
comment =>
|
||||
comment.type === "CommentBlock" && comment.value === ` ${languageName} `
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function isPathMatch(path, predicateStack) {
|
||||
const stack = path.stack.slice();
|
||||
|
||||
let name = null;
|
||||
let node = stack.pop();
|
||||
|
||||
for (const predicate of predicateStack) {
|
||||
if (node === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// skip index/array
|
||||
if (typeof name === "number") {
|
||||
name = stack.pop();
|
||||
node = stack.pop();
|
||||
}
|
||||
|
||||
if (!predicate(node, name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
name = stack.pop();
|
||||
node = stack.pop();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* - html`...`
|
||||
* - HTML comment block
|
||||
*/
|
||||
function isHtml(path) {
|
||||
const node = path.getValue();
|
||||
return (
|
||||
hasLanguageComment(node, "HTML") ||
|
||||
isPathMatch(path, [
|
||||
node => node.type === "TemplateLiteral",
|
||||
(node, name) =>
|
||||
node.type === "TaggedTemplateExpression" &&
|
||||
node.tag.type === "Identifier" &&
|
||||
node.tag.name === "html" &&
|
||||
name === "quasi"
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
function printHtmlTemplateLiteral(path, print, textToDoc, parser) {
|
||||
const node = path.getValue();
|
||||
|
||||
const placeholderPattern =
|
||||
"prettierhtmlplaceholder(\\d+)redlohecalplmthreitterp";
|
||||
const placeholders = node.expressions.map(
|
||||
(_, i) => `prettierhtmlplaceholder${i}redlohecalplmthreitterp`
|
||||
);
|
||||
|
||||
const text = node.quasis
|
||||
.map(
|
||||
(quasi, index, quasis) =>
|
||||
index === quasis.length - 1
|
||||
? quasi.value.raw
|
||||
: quasi.value.raw + placeholders[index]
|
||||
)
|
||||
.join("");
|
||||
|
||||
const expressionDocs = path.map(print, "expressions");
|
||||
|
||||
const contentDoc = mapDoc(
|
||||
stripTrailingHardline(textToDoc(text, { parser })),
|
||||
doc => {
|
||||
const placeholderRegex = new RegExp(placeholderPattern, "g");
|
||||
const hasPlaceholder =
|
||||
typeof doc === "string" && placeholderRegex.test(doc);
|
||||
|
||||
if (!hasPlaceholder) {
|
||||
return doc;
|
||||
}
|
||||
|
||||
const parts = [];
|
||||
|
||||
const components = doc.split(placeholderRegex);
|
||||
for (let i = 0; i < components.length; i++) {
|
||||
const component = components[i];
|
||||
|
||||
if (i % 2 === 0) {
|
||||
if (component) {
|
||||
parts.push(component);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const placeholderIndex = +component;
|
||||
|
||||
parts.push(
|
||||
concat([
|
||||
"${",
|
||||
group(
|
||||
concat([
|
||||
indent(concat([softline, expressionDocs[placeholderIndex]])),
|
||||
softline
|
||||
])
|
||||
),
|
||||
"}"
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
return concat(parts);
|
||||
}
|
||||
);
|
||||
|
||||
return concat(["`", indent(concat([hardline, contentDoc])), softline, "`"]);
|
||||
}
|
||||
|
||||
module.exports = embed;
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
"use strict";
|
||||
|
||||
const {
|
||||
builders: { concat, join, line }
|
||||
} = require("../doc");
|
||||
|
||||
function printHtmlBinding(path, options, print) {
|
||||
const node = path.getValue();
|
||||
|
||||
if (options.__onHtmlBindingRoot && path.getName() === null) {
|
||||
options.__onHtmlBindingRoot(node);
|
||||
}
|
||||
|
||||
if (node.type !== "File") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.__isVueForBindingLeft) {
|
||||
return path.call(
|
||||
functionDeclarationPath => {
|
||||
const { params } = functionDeclarationPath.getValue();
|
||||
return concat([
|
||||
params.length > 1 ? "(" : "",
|
||||
join(
|
||||
concat([",", line]),
|
||||
functionDeclarationPath.map(print, "params")
|
||||
),
|
||||
params.length > 1 ? ")" : ""
|
||||
]);
|
||||
},
|
||||
"program",
|
||||
"body",
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
if (options.__isVueSlotScope) {
|
||||
return path.call(
|
||||
functionDeclarationPath =>
|
||||
join(concat([",", line]), functionDeclarationPath.map(print, "params")),
|
||||
"program",
|
||||
"body",
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
printHtmlBinding
|
||||
};
|
|
@ -500,6 +500,8 @@ function needsParens(path, options) {
|
|||
return false;
|
||||
} else if (parent.type === "Property" && parent.value === node) {
|
||||
return false;
|
||||
} else if (parent.type === "NGChainedExpression") {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -613,6 +615,21 @@ function needsParens(path, options) {
|
|||
return true;
|
||||
}
|
||||
return false;
|
||||
case "NGPipeExpression":
|
||||
if (
|
||||
parent.type === "NGRoot" ||
|
||||
parent.type === "ObjectProperty" ||
|
||||
parent.type === "ArrayExpression" ||
|
||||
((parent.type === "CallExpression" ||
|
||||
parent.type === "OptionalCallExpression") &&
|
||||
parent.arguments[name] === node) ||
|
||||
(parent.type === "NGPipeExpression" && name === "right") ||
|
||||
(parent.type === "MemberExpression" && name === "property") ||
|
||||
parent.type === "AssignmentExpression"
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
"use strict";
|
||||
|
||||
const locFns = require("./loc");
|
||||
|
||||
function createParser(_parse) {
|
||||
const parse = (text, parsers, options) => {
|
||||
const ngEstreeParser = require("angular-estree-parser");
|
||||
const node = _parse(text, ngEstreeParser);
|
||||
return {
|
||||
type: "NGRoot",
|
||||
node:
|
||||
options.parser === "__ng_action" && node.type !== "NGChainedExpression"
|
||||
? Object.assign({}, node, {
|
||||
type: "NGChainedExpression",
|
||||
expressions: [node]
|
||||
})
|
||||
: node
|
||||
};
|
||||
};
|
||||
return Object.assign({ astFormat: "estree", parse }, locFns);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
parsers: {
|
||||
__ng_action: createParser((text, ng) => ng.parseAction(text)),
|
||||
__ng_binding: createParser((text, ng) => ng.parseBinding(text)),
|
||||
__ng_interpolation: createParser((text, ng) => ng.parseInterpolation(text)),
|
||||
__ng_directive: createParser((text, ng) => ng.parseTemplateBindings(text))
|
||||
}
|
||||
};
|
|
@ -180,7 +180,9 @@ module.exports = {
|
|||
},
|
||||
locFns
|
||||
),
|
||||
/** @internal for mdx to print jsx without semicolon */
|
||||
__js_expression: babylon
|
||||
/** @internal */
|
||||
__js_expression: babylon,
|
||||
/** for vue filter */
|
||||
__vue_expression: babylon
|
||||
}
|
||||
};
|
||||
|
|
|
@ -5,9 +5,12 @@ function preprocess(ast, options) {
|
|||
case "json":
|
||||
case "json5":
|
||||
case "json-stringify":
|
||||
case "__js_expression":
|
||||
case "__vue_expression":
|
||||
return Object.assign({}, ast, {
|
||||
type: "JsonRoot",
|
||||
node: Object.assign({}, ast, { comments: [] })
|
||||
type: options.parser.startsWith("__") ? "JsExpressionRoot" : "JsonRoot",
|
||||
node: ast,
|
||||
comments: []
|
||||
});
|
||||
default:
|
||||
return ast;
|
||||
|
|
|
@ -34,7 +34,9 @@ const insertPragma = require("./pragma").insertPragma;
|
|||
const handleComments = require("./comments");
|
||||
const pathNeedsParens = require("./needs-parens");
|
||||
const preprocess = require("./preprocess");
|
||||
const { printHtmlBinding } = require("./html-binding");
|
||||
const {
|
||||
hasNode,
|
||||
hasFlowAnnotationComment,
|
||||
hasFlowShorthandAnnotationComment
|
||||
} = require("./utils");
|
||||
|
@ -390,8 +392,15 @@ function printPathNoParens(path, options, print, args) {
|
|||
return n;
|
||||
}
|
||||
|
||||
const htmlBinding = printHtmlBinding(path, options, print);
|
||||
if (htmlBinding) {
|
||||
return htmlBinding;
|
||||
}
|
||||
|
||||
let parts = [];
|
||||
switch (n.type) {
|
||||
case "JsExpressionRoot":
|
||||
return path.call(print, "node");
|
||||
case "JsonRoot":
|
||||
return concat([path.call(print, "node"), hardline]);
|
||||
case "File":
|
||||
|
@ -464,7 +473,8 @@ function printPathNoParens(path, options, print, args) {
|
|||
options
|
||||
);
|
||||
case "BinaryExpression":
|
||||
case "LogicalExpression": {
|
||||
case "LogicalExpression":
|
||||
case "NGPipeExpression": {
|
||||
const parent = path.getParentNode();
|
||||
const parentParent = path.getParentNode(1);
|
||||
const isInsideParenthesis =
|
||||
|
@ -519,6 +529,11 @@ function printPathNoParens(path, options, print, args) {
|
|||
parent.type === "ReturnStatement" ||
|
||||
(parent.type === "JSXExpressionContainer" &&
|
||||
parentParent.type === "JSXAttribute") ||
|
||||
(n.type !== "NGPipeExpression" &&
|
||||
((parent.type === "NGRoot" && options.parser === "__ng_binding") ||
|
||||
(parent.type === "NGMicrosyntaxExpression" &&
|
||||
parentParent.type === "NGMicrosyntax" &&
|
||||
parentParent.body.length === 1))) ||
|
||||
(n === parent.body && parent.type === "ArrowFunctionExpression") ||
|
||||
(n !== parent.body && parent.type === "ForStatement") ||
|
||||
(parent.type === "ConditionalExpression" &&
|
||||
|
@ -3386,12 +3401,108 @@ function printPathNoParens(path, options, print, args) {
|
|||
|
||||
return concat(parts);
|
||||
|
||||
case "NGRoot":
|
||||
return concat(
|
||||
[].concat(
|
||||
path.call(print, "node"),
|
||||
!n.node.comments || n.node.comments.length === 0
|
||||
? []
|
||||
: concat([" //", n.node.comments[0].value.trimRight()])
|
||||
)
|
||||
);
|
||||
case "NGChainedExpression":
|
||||
return group(
|
||||
join(
|
||||
concat([";", line]),
|
||||
path.map(
|
||||
childPath =>
|
||||
hasNgSideEffect(childPath)
|
||||
? print(childPath)
|
||||
: concat(["(", print(childPath), ")"]),
|
||||
"expressions"
|
||||
)
|
||||
)
|
||||
);
|
||||
case "NGEmptyExpression":
|
||||
return "";
|
||||
case "NGQuotedExpression":
|
||||
return concat([n.prefix, ":", n.value]);
|
||||
case "NGMicrosyntax":
|
||||
return concat(
|
||||
path.map(
|
||||
(childPath, index) =>
|
||||
concat([
|
||||
index === 0
|
||||
? ""
|
||||
: isNgForOf(childPath)
|
||||
? " "
|
||||
: concat([";", line]),
|
||||
print(childPath)
|
||||
]),
|
||||
"body"
|
||||
)
|
||||
);
|
||||
case "NGMicrosyntaxKey":
|
||||
return /^[a-z_$][a-z0-9_$]*(-[a-z_$][a-z0-9_$])*$/i.test(n.name)
|
||||
? n.name
|
||||
: JSON.stringify(n.name);
|
||||
case "NGMicrosyntaxExpression":
|
||||
return concat([
|
||||
path.call(print, "expression"),
|
||||
n.alias === null ? "" : concat([" as ", path.call(print, "alias")])
|
||||
]);
|
||||
case "NGMicrosyntaxKeyedExpression":
|
||||
return concat([
|
||||
path.call(print, "key"),
|
||||
isNgForOf(path) ? " " : ": ",
|
||||
path.call(print, "expression")
|
||||
]);
|
||||
case "NGMicrosyntaxLet":
|
||||
return concat([
|
||||
"let ",
|
||||
path.call(print, "key"),
|
||||
n.value === null ? "" : concat([" = ", path.call(print, "value")])
|
||||
]);
|
||||
case "NGMicrosyntaxAs":
|
||||
return concat([
|
||||
path.call(print, "key"),
|
||||
" as ",
|
||||
path.call(print, "alias")
|
||||
]);
|
||||
default:
|
||||
/* istanbul ignore next */
|
||||
throw new Error("unknown type: " + JSON.stringify(n.type));
|
||||
}
|
||||
}
|
||||
|
||||
/** prefer `let hero of heros` over `let hero; of: heros` */
|
||||
function isNgForOf(path) {
|
||||
const node = path.getValue();
|
||||
const index = path.getName();
|
||||
const parentNode = path.getParentNode();
|
||||
return (
|
||||
node.type === "NGMicrosyntaxKeyedExpression" &&
|
||||
node.key.name === "of" &&
|
||||
index === 1 &&
|
||||
parentNode.body[0].type === "NGMicrosyntaxLet" &&
|
||||
parentNode.body[0].value === null
|
||||
);
|
||||
}
|
||||
|
||||
/** identify if an angular expression seems to have side effects */
|
||||
function hasNgSideEffect(path) {
|
||||
return hasNode(path.getValue(), node => {
|
||||
switch (node.type) {
|
||||
case undefined:
|
||||
return false;
|
||||
case "CallExpression":
|
||||
case "OptionalCallExpression":
|
||||
case "AssignmentExpression":
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function printStatementSequence(path, options, print) {
|
||||
const printed = [];
|
||||
|
||||
|
@ -5302,7 +5413,8 @@ function maybeWrapJSXElementInParens(path, elem) {
|
|||
ExpressionStatement: true,
|
||||
CallExpression: true,
|
||||
OptionalCallExpression: true,
|
||||
ConditionalExpression: true
|
||||
ConditionalExpression: true,
|
||||
JsExpressionRoot: true
|
||||
};
|
||||
if (NO_WRAP_PARENTS[parent.type]) {
|
||||
return elem;
|
||||
|
@ -5327,7 +5439,11 @@ function maybeWrapJSXElementInParens(path, elem) {
|
|||
}
|
||||
|
||||
function isBinaryish(node) {
|
||||
return node.type === "BinaryExpression" || node.type === "LogicalExpression";
|
||||
return (
|
||||
node.type === "BinaryExpression" ||
|
||||
node.type === "LogicalExpression" ||
|
||||
node.type === "NGPipeExpression"
|
||||
);
|
||||
}
|
||||
|
||||
function isMemberish(node) {
|
||||
|
@ -5414,16 +5530,36 @@ function printBinaryishExpressions(
|
|||
|
||||
const shouldInline = shouldInlineLogicalExpression(node);
|
||||
const lineBeforeOperator =
|
||||
node.operator === "|>" &&
|
||||
(node.operator === "|>" ||
|
||||
node.type === "NGPipeExpression" ||
|
||||
(node.operator === "|" && options.parser === "__vue_expression")) &&
|
||||
!hasLeadingOwnLineComment(options.originalText, node.right, options);
|
||||
|
||||
const operator = node.type === "NGPipeExpression" ? "|" : node.operator;
|
||||
const rightSuffix =
|
||||
node.type === "NGPipeExpression" && node.arguments.length !== 0
|
||||
? group(
|
||||
indent(
|
||||
concat([
|
||||
softline,
|
||||
": ",
|
||||
join(
|
||||
concat([softline, ":", ifBreak(" ")]),
|
||||
path.map(print, "arguments").map(arg => align(2, group(arg)))
|
||||
)
|
||||
])
|
||||
)
|
||||
)
|
||||
: "";
|
||||
|
||||
const right = shouldInline
|
||||
? concat([node.operator, " ", path.call(print, "right")])
|
||||
? concat([operator, " ", path.call(print, "right"), rightSuffix])
|
||||
: concat([
|
||||
lineBeforeOperator ? softline : "",
|
||||
node.operator,
|
||||
operator,
|
||||
lineBeforeOperator ? " " : line,
|
||||
path.call(print, "right")
|
||||
path.call(print, "right"),
|
||||
rightSuffix
|
||||
]);
|
||||
|
||||
// If there's only a single binary expression, we want to create a group
|
||||
|
@ -5567,6 +5703,7 @@ function hasNakedLeftSide(node) {
|
|||
node.type === "AssignmentExpression" ||
|
||||
node.type === "BinaryExpression" ||
|
||||
node.type === "LogicalExpression" ||
|
||||
node.type === "NGPipeExpression" ||
|
||||
node.type === "ConditionalExpression" ||
|
||||
node.type === "CallExpression" ||
|
||||
node.type === "OptionalCallExpression" ||
|
||||
|
|
|
@ -31,7 +31,21 @@ function hasFlowAnnotationComment(comments) {
|
|||
return comments && comments[0].value.match(FLOW_ANNOTATION);
|
||||
}
|
||||
|
||||
function hasNode(node, fn) {
|
||||
if (!node || typeof node !== "object") {
|
||||
return false;
|
||||
}
|
||||
if (Array.isArray(node)) {
|
||||
return node.some(value => hasNode(value, fn));
|
||||
}
|
||||
const result = fn(node);
|
||||
return typeof result === "boolean"
|
||||
? result
|
||||
: Object.keys(node).some(key => hasNode(node[key], fn));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
hasNode,
|
||||
hasFlowShorthandAnnotationComment,
|
||||
hasFlowAnnotationComment
|
||||
};
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
const { concat, hardline } = require("../doc").builders;
|
||||
|
||||
function embed(path, print, textToDoc, options) {
|
||||
const node = path.getValue();
|
||||
const parent = path.getParentNode();
|
||||
if (!parent || parent.tag !== "root" || node.unary) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let parser;
|
||||
|
||||
if (node.tag === "style") {
|
||||
const langAttr = node.attrs.find(attr => attr.name === "lang");
|
||||
if (!langAttr || langAttr.value === "postcss") {
|
||||
parser = "css";
|
||||
} else if (langAttr.value === "scss") {
|
||||
parser = "scss";
|
||||
} else if (langAttr.value === "less") {
|
||||
parser = "less";
|
||||
}
|
||||
}
|
||||
|
||||
if (node.tag === "script" && !node.attrs.some(attr => attr.name === "src")) {
|
||||
const langAttr = node.attrs.find(attr => attr.name === "lang");
|
||||
if (!langAttr) {
|
||||
parser = "babylon";
|
||||
} else if (langAttr.value === "ts" || langAttr.value === "tsx") {
|
||||
parser = "typescript";
|
||||
}
|
||||
}
|
||||
|
||||
if (!parser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return concat([
|
||||
options.originalText.slice(node.start, node.contentStart),
|
||||
hardline,
|
||||
textToDoc(options.originalText.slice(node.contentStart, node.contentEnd), {
|
||||
parser
|
||||
}),
|
||||
options.originalText.slice(node.contentEnd, node.end)
|
||||
]);
|
||||
}
|
||||
|
||||
module.exports = embed;
|
|
@ -1,23 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
const printer = require("./printer-vue");
|
||||
const createLanguage = require("../utils/create-language");
|
||||
|
||||
const languages = [
|
||||
createLanguage(require("linguist-languages/data/vue"), {
|
||||
override: {
|
||||
since: "1.10.0",
|
||||
parsers: ["vue"],
|
||||
vscodeLanguageIds: ["vue"]
|
||||
}
|
||||
})
|
||||
];
|
||||
|
||||
const printers = {
|
||||
vue: printer
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
languages,
|
||||
printers
|
||||
};
|
|
@ -1,425 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
const { hasPragma } = require("./pragma");
|
||||
|
||||
/*!
|
||||
* Extracted from vue codebase
|
||||
* https://github.com/vuejs/vue/blob/cfd73c2386623341fdbb3ac636c4baf84ea89c2c/src/compiler/parser/html-parser.js
|
||||
* HTML Parser By John Resig (ejohn.org)
|
||||
* Modified by Juriy "kangax" Zaytsev
|
||||
* Original code by Erik Arvidsson, Mozilla Public License
|
||||
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
|
||||
*/
|
||||
|
||||
/**
|
||||
* Make a map and return a function for checking if a key
|
||||
* is in that map.
|
||||
*/
|
||||
function makeMap(str, expectsLowerCase) {
|
||||
const map = Object.create(null);
|
||||
const list = str.split(",");
|
||||
const listLength = list.length;
|
||||
for (let i = 0; i < listLength; i++) {
|
||||
map[list[i]] = true;
|
||||
}
|
||||
return expectsLowerCase ? val => map[val.toLowerCase()] : val => map[val];
|
||||
}
|
||||
|
||||
/**
|
||||
* Always return false.
|
||||
*/
|
||||
const no = () => false;
|
||||
|
||||
// HTML5 tags https://html.spec.whatwg.org/multipage/indices.html#elements-3
|
||||
// Phrasing Content https://html.spec.whatwg.org/multipage/dom.html#phrasing-content
|
||||
const isNonPhrasingTag = makeMap(
|
||||
"address,article,aside,base,blockquote,body,caption,col,colgroup,dd," +
|
||||
"details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form," +
|
||||
"h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta," +
|
||||
"optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead," +
|
||||
"title,tr,track"
|
||||
);
|
||||
|
||||
// Regular Expressions for parsing tags and attributes
|
||||
const attribute = /^\s*([^\s"'<>/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
|
||||
// could use https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-QName
|
||||
// but for Vue templates we can enforce a simple charset
|
||||
const ncname = "[a-zA-Z_][\\w\\-\\.]*";
|
||||
const qnameCapture = `((?:${ncname}\\:)?${ncname})`;
|
||||
const startTagOpen = new RegExp(`^<${qnameCapture}`);
|
||||
const startTagClose = /^\s*(\/?)>/;
|
||||
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`);
|
||||
const doctype = /^<!DOCTYPE [^>]+>/i;
|
||||
const comment = /^<!--/;
|
||||
const conditionalComment = /^<!\[/;
|
||||
|
||||
let IS_REGEX_CAPTURING_BROKEN = false;
|
||||
"x".replace(/x(.)?/g, (m, g) => {
|
||||
IS_REGEX_CAPTURING_BROKEN = g === "";
|
||||
});
|
||||
|
||||
// Special Elements (can contain anything)
|
||||
const isPlainTextElement = makeMap("script,style,textarea", true);
|
||||
const reCache = {};
|
||||
|
||||
const decodingMap = {
|
||||
"<": "<",
|
||||
">": ">",
|
||||
""": '"',
|
||||
"&": "&",
|
||||
" ": "\n",
|
||||
"	": "\t"
|
||||
};
|
||||
const encodedAttr = /&(?:lt|gt|quot|amp);/g;
|
||||
const encodedAttrWithNewLines = /&(?:lt|gt|quot|amp|#10|#9);/g;
|
||||
|
||||
// #5992
|
||||
const isIgnoreNewlineTag = makeMap("pre,textarea", true);
|
||||
const shouldIgnoreFirstNewline = (tag, html) =>
|
||||
tag && isIgnoreNewlineTag(tag) && html[0] === "\n";
|
||||
|
||||
function decodeAttr(value, shouldDecodeNewlines) {
|
||||
const re = shouldDecodeNewlines ? encodedAttrWithNewLines : encodedAttr;
|
||||
return value.replace(re, match => decodingMap[match]);
|
||||
}
|
||||
|
||||
function parseHTML(html, options) {
|
||||
const stack = [];
|
||||
const expectHTML = options.expectHTML;
|
||||
const isUnaryTag = options.isUnaryTag || no;
|
||||
const canBeLeftOpenTag = options.canBeLeftOpenTag || no;
|
||||
let index = 0;
|
||||
let last;
|
||||
let lastTag;
|
||||
while (html) {
|
||||
last = html;
|
||||
// Make sure we're not in a plaintext content element like script/style
|
||||
if (!lastTag || !isPlainTextElement(lastTag)) {
|
||||
let textEnd = html.indexOf("<");
|
||||
if (textEnd === 0) {
|
||||
// Comment:
|
||||
if (comment.test(html)) {
|
||||
const commentEnd = html.indexOf("-->");
|
||||
|
||||
if (commentEnd >= 0) {
|
||||
if (options.shouldKeepComment) {
|
||||
options.comment(html.substring(4, commentEnd));
|
||||
}
|
||||
advance(commentEnd + 3);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
|
||||
if (conditionalComment.test(html)) {
|
||||
const conditionalEnd = html.indexOf("]>");
|
||||
|
||||
if (conditionalEnd >= 0) {
|
||||
advance(conditionalEnd + 2);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Doctype:
|
||||
const doctypeMatch = html.match(doctype);
|
||||
if (doctypeMatch) {
|
||||
advance(doctypeMatch[0].length);
|
||||
continue;
|
||||
}
|
||||
|
||||
// End tag:
|
||||
const endTagMatch = html.match(endTag);
|
||||
if (endTagMatch) {
|
||||
const curIndex = index;
|
||||
advance(endTagMatch[0].length);
|
||||
parseEndTag(endTagMatch[1], curIndex, index);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Start tag:
|
||||
const startTagMatch = parseStartTag();
|
||||
if (startTagMatch) {
|
||||
handleStartTag(startTagMatch);
|
||||
if (shouldIgnoreFirstNewline(lastTag, html)) {
|
||||
advance(1);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let text;
|
||||
let rest;
|
||||
let next;
|
||||
|
||||
if (textEnd >= 0) {
|
||||
rest = html.slice(textEnd);
|
||||
while (
|
||||
!endTag.test(rest) &&
|
||||
!startTagOpen.test(rest) &&
|
||||
!comment.test(rest) &&
|
||||
!conditionalComment.test(rest)
|
||||
) {
|
||||
// < in plain text, be forgiving and treat it as text
|
||||
next = rest.indexOf("<", 1);
|
||||
if (next < 0) {
|
||||
break;
|
||||
}
|
||||
textEnd += next;
|
||||
rest = html.slice(textEnd);
|
||||
}
|
||||
text = html.substring(0, textEnd);
|
||||
advance(textEnd);
|
||||
}
|
||||
|
||||
if (textEnd < 0) {
|
||||
text = html;
|
||||
html = "";
|
||||
}
|
||||
|
||||
if (options.chars && text) {
|
||||
options.chars(text);
|
||||
}
|
||||
} else {
|
||||
let endTagLength = 0;
|
||||
const stackedTag = lastTag.toLowerCase();
|
||||
const reStackedTag =
|
||||
reCache[stackedTag] ||
|
||||
(reCache[stackedTag] = new RegExp(
|
||||
"([\\s\\S]*?)(</" + stackedTag + "[^>]*>)",
|
||||
"i"
|
||||
));
|
||||
const rest = html.replace(reStackedTag, (all, text, endTag) => {
|
||||
endTagLength = endTag.length;
|
||||
if (!isPlainTextElement(stackedTag) && stackedTag !== "noscript") {
|
||||
text = text
|
||||
.replace(/<!--([\s\S]*?)-->/g, "$1")
|
||||
.replace(/<!\[CDATA\[([\s\S]*?)]]>/g, "$1");
|
||||
}
|
||||
if (shouldIgnoreFirstNewline(stackedTag, text)) {
|
||||
text = text.slice(1);
|
||||
}
|
||||
if (options.chars) {
|
||||
options.chars(text);
|
||||
}
|
||||
return "";
|
||||
});
|
||||
index += html.length - rest.length;
|
||||
html = rest;
|
||||
parseEndTag(stackedTag, index - endTagLength, index);
|
||||
}
|
||||
|
||||
if (html === last) {
|
||||
options.chars && options.chars(html);
|
||||
if (
|
||||
process.env.NODE_ENV !== "production" &&
|
||||
!stack.length &&
|
||||
options.warn
|
||||
) {
|
||||
options.warn(`Mal-formatted tag at end of template: "${html}"`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up any remaining tags
|
||||
parseEndTag();
|
||||
|
||||
function advance(n) {
|
||||
index += n;
|
||||
html = html.substring(n);
|
||||
}
|
||||
|
||||
function parseStartTag() {
|
||||
const start = html.match(startTagOpen);
|
||||
if (start) {
|
||||
const match = {
|
||||
tagName: start[1],
|
||||
attrs: [],
|
||||
start: index
|
||||
};
|
||||
advance(start[0].length);
|
||||
let end;
|
||||
let attr;
|
||||
while (
|
||||
!(end = html.match(startTagClose)) &&
|
||||
(attr = html.match(attribute))
|
||||
) {
|
||||
advance(attr[0].length);
|
||||
match.attrs.push(attr);
|
||||
}
|
||||
if (end) {
|
||||
match.unarySlash = end[1];
|
||||
advance(end[0].length);
|
||||
match.end = index;
|
||||
return match;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleStartTag(match) {
|
||||
const { tagName, unarySlash } = match;
|
||||
|
||||
if (expectHTML) {
|
||||
if (lastTag === "p" && isNonPhrasingTag(tagName)) {
|
||||
parseEndTag(lastTag);
|
||||
}
|
||||
if (canBeLeftOpenTag(tagName) && lastTag === tagName) {
|
||||
parseEndTag(tagName);
|
||||
}
|
||||
}
|
||||
|
||||
const unary = isUnaryTag(tagName) || !!unarySlash;
|
||||
|
||||
const l = match.attrs.length;
|
||||
const attrs = new Array(l);
|
||||
for (let i = 0; i < l; i++) {
|
||||
const args = match.attrs[i];
|
||||
// hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778
|
||||
if (IS_REGEX_CAPTURING_BROKEN && args[0].indexOf('""') === -1) {
|
||||
if (args[3] === "") {
|
||||
delete args[3];
|
||||
}
|
||||
if (args[4] === "") {
|
||||
delete args[4];
|
||||
}
|
||||
if (args[5] === "") {
|
||||
delete args[5];
|
||||
}
|
||||
}
|
||||
const value = args[3] || args[4] || args[5] || "";
|
||||
const shouldDecodeNewlines =
|
||||
tagName === "a" && args[1] === "href"
|
||||
? options.shouldDecodeNewlinesForHref
|
||||
: options.shouldDecodeNewlines;
|
||||
attrs[i] = {
|
||||
name: args[1],
|
||||
value: decodeAttr(value, shouldDecodeNewlines)
|
||||
};
|
||||
}
|
||||
|
||||
if (!unary) {
|
||||
stack.push({
|
||||
tag: tagName,
|
||||
lowerCasedTag: tagName.toLowerCase(),
|
||||
attrs: attrs
|
||||
});
|
||||
lastTag = tagName;
|
||||
}
|
||||
|
||||
if (options.start) {
|
||||
options.start(tagName, attrs, unary, match.start, match.end);
|
||||
}
|
||||
}
|
||||
|
||||
function parseEndTag(tagName, start, end) {
|
||||
let pos;
|
||||
let lowerCasedTagName;
|
||||
if (start == null) {
|
||||
start = index;
|
||||
}
|
||||
if (end == null) {
|
||||
end = index;
|
||||
}
|
||||
|
||||
if (tagName) {
|
||||
lowerCasedTagName = tagName.toLowerCase();
|
||||
}
|
||||
|
||||
// Find the closest opened tag of the same type
|
||||
if (tagName) {
|
||||
for (pos = stack.length - 1; pos >= 0; pos--) {
|
||||
if (stack[pos].lowerCasedTag === lowerCasedTagName) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If no tag name is provided, clean shop
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
if (pos >= 0) {
|
||||
// Close all the open elements, up the stack
|
||||
for (let i = stack.length - 1; i >= pos; i--) {
|
||||
if (
|
||||
process.env.NODE_ENV !== "production" &&
|
||||
(i > pos || !tagName) &&
|
||||
options.warn
|
||||
) {
|
||||
options.warn(`tag <${stack[i].tag}> has no matching end tag.`);
|
||||
}
|
||||
if (options.end) {
|
||||
options.end(stack[i].tag, start, end);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the open elements from the stack
|
||||
stack.length = pos;
|
||||
lastTag = pos && stack[pos - 1].tag;
|
||||
} else if (lowerCasedTagName === "br") {
|
||||
if (options.start) {
|
||||
options.start(tagName, [], true, start, end);
|
||||
}
|
||||
} else if (lowerCasedTagName === "p") {
|
||||
if (options.start) {
|
||||
options.start(tagName, [], false, start, end);
|
||||
}
|
||||
if (options.end) {
|
||||
options.end(tagName, start, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parse(text /*, parsers, opts*/) {
|
||||
const rootObj = {
|
||||
tag: "root",
|
||||
attrs: [],
|
||||
unary: false,
|
||||
start: 0,
|
||||
contentStart: 0,
|
||||
contentEnd: text.length,
|
||||
end: text.length,
|
||||
children: [],
|
||||
comments: []
|
||||
};
|
||||
const objStack = [rootObj];
|
||||
let obj = rootObj;
|
||||
parseHTML(text, {
|
||||
start: function(tag, attrs, unary, start, end) {
|
||||
const newObj = {
|
||||
tag,
|
||||
attrs,
|
||||
unary,
|
||||
start,
|
||||
children: []
|
||||
};
|
||||
obj.children.push(newObj);
|
||||
if (unary) {
|
||||
newObj.end = end;
|
||||
} else {
|
||||
newObj.contentStart = end;
|
||||
objStack.push(newObj);
|
||||
obj = newObj;
|
||||
}
|
||||
},
|
||||
end: function(tag, start, end) {
|
||||
objStack.pop();
|
||||
obj.contentEnd = start;
|
||||
obj.end = end;
|
||||
obj = objStack[objStack.length - 1];
|
||||
}
|
||||
});
|
||||
return rootObj;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
parsers: {
|
||||
vue: {
|
||||
parse,
|
||||
hasPragma,
|
||||
astFormat: "vue",
|
||||
locStart: node => node.start,
|
||||
locEnd: node => node.end
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,45 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
const embed = require("./embed");
|
||||
const { concat, hardline } = require("../doc").builders;
|
||||
const { insertPragma } = require("./pragma");
|
||||
|
||||
function genericPrint(path, options, print) {
|
||||
const n = path.getValue();
|
||||
const res = [];
|
||||
let index = n.start;
|
||||
|
||||
path.each(childPath => {
|
||||
const child = childPath.getValue();
|
||||
res.push(options.originalText.slice(index, child.start));
|
||||
res.push(childPath.call(print));
|
||||
index = child.end;
|
||||
}, "children");
|
||||
|
||||
// If there are no children, we just print the node from start to end.
|
||||
// Otherwise, index should point to the end of the last child, and we
|
||||
// need to print the closing tag.
|
||||
res.push(options.originalText.slice(index, n.end));
|
||||
|
||||
// Only force a trailing newline if there were any contents.
|
||||
if (n.tag === "root" && n.children.length) {
|
||||
res.push(hardline);
|
||||
}
|
||||
|
||||
return concat(res);
|
||||
}
|
||||
|
||||
const clean = (ast, newObj) => {
|
||||
delete newObj.start;
|
||||
delete newObj.end;
|
||||
delete newObj.contentStart;
|
||||
delete newObj.contentEnd;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
print: genericPrint,
|
||||
embed,
|
||||
insertPragma,
|
||||
massageAstNode: clean,
|
||||
canAttachComment: node => typeof node.tag === "string"
|
||||
};
|
|
@ -181,12 +181,20 @@ function attach(comments, ast, text, options) {
|
|||
|
||||
comments.forEach((comment, i) => {
|
||||
if (
|
||||
(options.parser === "json" || options.parser === "json5") &&
|
||||
locStart(comment) - locStart(ast) <= 0
|
||||
options.parser === "json" ||
|
||||
options.parser === "json5" ||
|
||||
options.parser === "__js_expression" ||
|
||||
options.parser === "__vue_expression"
|
||||
) {
|
||||
if (locStart(comment) - locStart(ast) <= 0) {
|
||||
addLeadingComment(ast, comment);
|
||||
return;
|
||||
}
|
||||
if (locEnd(comment) - locEnd(ast) >= 0) {
|
||||
addTrailingComment(ast, comment);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
decorateComment(ast, comment, options);
|
||||
const { precedingNode, enclosingNode, followingNode } = comment;
|
||||
|
|
|
@ -122,7 +122,8 @@ const options = {
|
|||
since: null,
|
||||
description: "Handlebars"
|
||||
},
|
||||
{ value: "html", since: "1.15.0", description: "HTML" }
|
||||
{ value: "html", since: "1.15.0", description: "HTML" },
|
||||
{ value: "angular", since: "1.15.0", description: "Angular" }
|
||||
]
|
||||
},
|
||||
plugins: {
|
||||
|
|
|
@ -15,7 +15,6 @@ const internalPlugins = [
|
|||
require("./language-html"),
|
||||
require("./language-js"),
|
||||
require("./language-markdown"),
|
||||
require("./language-vue"),
|
||||
require("./language-yaml")
|
||||
];
|
||||
|
||||
|
|
|
@ -21,7 +21,9 @@ class TestComponent {}
|
|||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@Component({
|
||||
selector: "app-test",
|
||||
template: \`<ul> <li>test</li>
|
||||
template: \`
|
||||
<ul>
|
||||
<li>test</li>
|
||||
</ul>
|
||||
\`,
|
||||
styles: [
|
||||
|
@ -60,7 +62,9 @@ class TestComponent {}
|
|||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@Component({
|
||||
selector: "app-test",
|
||||
template: \`<ul> <li>test</li>
|
||||
template: \`
|
||||
<ul>
|
||||
<li>test</li>
|
||||
</ul>
|
||||
\`,
|
||||
styles: [
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`logical-expression.ng - __ng_interpolation-verify 1`] = `
|
||||
[
|
||||
advancedSearchService.patientInformationFieldsRow2 && advancedSearchService.patientInformationFieldsRow2.indexOf(advancedSearchService.formElementData.customFieldList[i].customFieldType) !== -1
|
||||
]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
[
|
||||
advancedSearchService.patientInformationFieldsRow2 &&
|
||||
advancedSearchService.patientInformationFieldsRow2.indexOf(
|
||||
advancedSearchService.formElementData.customFieldList[i].customFieldType
|
||||
) !== -1
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`pipe-expression.ng - __ng_interpolation-verify 1`] = `
|
||||
[
|
||||
a ? (b | c : d) : (e | f : g),
|
||||
a | b | c | d,
|
||||
((a | b) | c) | d,
|
||||
a | b:(c | d),
|
||||
{ a: b | c },
|
||||
(a + b) | c,
|
||||
(a | b) + c,
|
||||
fn(a | b),
|
||||
a?.b(c | d),
|
||||
a[b | c],
|
||||
($students | async).items,
|
||||
($students | async)(),
|
||||
myData | myPipe:'arg1':'arg2':'arg3',
|
||||
value
|
||||
| pipeA: {
|
||||
keyA: reallySuperLongValue,
|
||||
keyB: shortValue | pipeB | pipeC: valueToPipeC
|
||||
} : {
|
||||
keyA: reallySuperLongValue,
|
||||
keyB: shortValue | pipeB | pipeC: valueToPipeC
|
||||
}
|
||||
| aaa
|
||||
]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
[
|
||||
a ? (b | c: d) : (e | f: g),
|
||||
a | b | c | d,
|
||||
a | b | c | d,
|
||||
a | b: (c | d),
|
||||
{ a: b | c },
|
||||
a + b | c,
|
||||
(a | b) + c,
|
||||
fn(a | b),
|
||||
a?.b(c | d),
|
||||
a[b | c],
|
||||
($students | async).items,
|
||||
($students | async)(),
|
||||
myData | myPipe: "arg1":"arg2":"arg3",
|
||||
value
|
||||
| pipeA
|
||||
: {
|
||||
keyA: reallySuperLongValue,
|
||||
keyB: shortValue | pipeB | pipeC: valueToPipeC
|
||||
}
|
||||
: {
|
||||
keyA: reallySuperLongValue,
|
||||
keyB: shortValue | pipeB | pipeC: valueToPipeC
|
||||
}
|
||||
| aaa
|
||||
]
|
||||
`;
|
|
@ -0,0 +1 @@
|
|||
run_spec(__dirname, ["__ng_interpolation"]);
|
|
@ -0,0 +1,3 @@
|
|||
[
|
||||
advancedSearchService.patientInformationFieldsRow2 && advancedSearchService.patientInformationFieldsRow2.indexOf(advancedSearchService.formElementData.customFieldList[i].customFieldType) !== -1
|
||||
]
|
|
@ -0,0 +1,24 @@
|
|||
[
|
||||
a ? (b | c : d) : (e | f : g),
|
||||
a | b | c | d,
|
||||
((a | b) | c) | d,
|
||||
a | b:(c | d),
|
||||
{ a: b | c },
|
||||
(a + b) | c,
|
||||
(a | b) + c,
|
||||
fn(a | b),
|
||||
a?.b(c | d),
|
||||
a[b | c],
|
||||
($students | async).items,
|
||||
($students | async)(),
|
||||
myData | myPipe:'arg1':'arg2':'arg3',
|
||||
value
|
||||
| pipeA: {
|
||||
keyA: reallySuperLongValue,
|
||||
keyB: shortValue | pipeB | pipeC: valueToPipeC
|
||||
} : {
|
||||
keyA: reallySuperLongValue,
|
||||
keyB: shortValue | pipeB | pipeC: valueToPipeC
|
||||
}
|
||||
| aaa
|
||||
]
|
|
@ -53,7 +53,9 @@ export class HeroButtonComponent {
|
|||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@Component({
|
||||
selector: "toh-hero-button",
|
||||
template: \`<button>{{label}}</button>\`
|
||||
template: \`
|
||||
<button>{{ label }}</button>
|
||||
\`
|
||||
})
|
||||
export class HeroButtonComponent {
|
||||
@Output() change = new EventEmitter<any>();
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,81 @@
|
|||
<div
|
||||
bindon-target=" a | b : c "
|
||||
[(target)]=" a | b : c "
|
||||
bind-target=" a | b : c "
|
||||
[target]=" a | b : c "
|
||||
[target]=" 0 - 1 "
|
||||
[target]=" - 1 "
|
||||
[target]=" a ? 1 : 2 "
|
||||
[target]=" "
|
||||
[target]=" a ( 1 ) ( 2 ) "
|
||||
[target]=" a [ b ] "
|
||||
[target]=" [ 1 ] "
|
||||
[target]=" { 'a' : 1 } "
|
||||
[target]=" { a : 1 } "
|
||||
[target]=" {
|
||||
trailingComma : 'notAllowed'
|
||||
}"
|
||||
[target]=" [
|
||||
longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong
|
||||
]"
|
||||
[target]=" true "
|
||||
[target]=" undefined "
|
||||
[target]=" null "
|
||||
[target]=" ( 1 ) "
|
||||
[target]=" 1 "
|
||||
[target]=" 'hello' "
|
||||
[target]=" a ( 1 , 2 ) "
|
||||
[target]=" a . b ( 1 , 2 ) "
|
||||
[target]=" x ! "
|
||||
[target]=" ! x "
|
||||
[target]=" ( ( a ) ) "
|
||||
[target]=" a "
|
||||
[target]=" a // hello "
|
||||
[target]=" a . b "
|
||||
[target]=" javascript : 'hello world' "
|
||||
[target]=" a ?. b ( ) "
|
||||
[target]=" a ?. b "
|
||||
[target]=" this . a "
|
||||
on-target=" a = 1 "
|
||||
(target)=" a = 1 "
|
||||
(target)=" a . b = 1 "
|
||||
(target)=" a [ b ] = 1 "
|
||||
(target)=" a // hello "
|
||||
(target)=" a ; b "
|
||||
(event)=" 0 "
|
||||
(event)=" a.b "
|
||||
(event)=" hello "
|
||||
(event)=" hello() "
|
||||
(event)=" a && b "
|
||||
(event)=" a && b() "
|
||||
(event)=" foo = $event "
|
||||
(event)=" foo == $event "
|
||||
*ngIf=" something?true:false "
|
||||
*ngFor=" let hero of heroes"
|
||||
*ngFor=" let hero of[1,2,3,666666666666666666666666666666666666]; let i=index"
|
||||
*ngFor=" let hero of heroes; trackBy : trackByHeroes "
|
||||
*ngFor=" let item of items ; index as i ; trackBy : trackByFn"
|
||||
*ngFor=" let hero of heroes; let i = index"
|
||||
*ngFor=" let hero of heroes; value myValue"
|
||||
*ngIf=" condition ; then thenBlock else elseBlock "
|
||||
*ngIf=" condition as value ; else elseBlock "
|
||||
*directive=" let hero "
|
||||
*directive=" let hero = hello "
|
||||
*directive=" let hero of heroes "
|
||||
*directive=" let hero ; of : heroes "
|
||||
*directive=" a "
|
||||
*directive=" a as b "
|
||||
*directive=" a , b "
|
||||
*directive=" a ; b "
|
||||
*directive=" a ; b c "
|
||||
*directive=" a ; b : c "
|
||||
*directive=" a ; b : c as d "
|
||||
*directive=" a ; b as c "
|
||||
*ngIf="listRow.NextScheduledSendStatus == 1 || listRow.NextScheduledSendStatus == 2 || listRow.NextScheduledSendStatus == 3"
|
||||
*ngIf="listRow.NextScheduledSendStatus == 1 || listRow.NextScheduledSendStatus == 2 || listRow.NextScheduledSendStatus == 3; else hello"
|
||||
(target)="listRow.NextScheduledSendStatus == 1 || listRow.NextScheduledSendStatus == 2 || listRow.NextScheduledSendStatus == 3"
|
||||
[target]="listRow.NextScheduledSendStatus == 1 || listRow.NextScheduledSendStatus == 2 || listRow.NextScheduledSendStatus == 3"
|
||||
[target]="{ longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong: true }"
|
||||
[error]="'We couldn\\\'t find anything with that name.'"
|
||||
*ngIf="form.controls.details?.controls.amount?.errors.min"
|
||||
></div>
|
|
@ -0,0 +1,23 @@
|
|||
<div
|
||||
bindon-target=" a | b : c "
|
||||
[(target)]=" a | b : c "
|
||||
bind-target=" a | b : c "
|
||||
></div>
|
||||
<!-- prettier-ignore-attribute -->
|
||||
<div
|
||||
bindon-target=" a | b : c "
|
||||
[(target)]=" a | b : c "
|
||||
bind-target=" a | b : c "
|
||||
></div>
|
||||
<!-- prettier-ignore-attribute bind-target -->
|
||||
<div
|
||||
bindon-target=" a | b : c "
|
||||
[(target)]=" a | b : c "
|
||||
bind-target=" a | b : c "
|
||||
></div>
|
||||
<!-- prettier-ignore-attribute [(target)] bind-target -->
|
||||
<div
|
||||
bindon-target=" a | b : c "
|
||||
[(target)]=" a | b : c "
|
||||
bind-target=" a | b : c "
|
||||
></div>
|
|
@ -0,0 +1,52 @@
|
|||
<div>{{ a | b : c }}</div>
|
||||
<div>{{ 0 - 1 }}</div>
|
||||
<div>{{ - 1 }}</div>
|
||||
<div>{{ a ? 1 : 2 }}</div>
|
||||
<div>{{ a ( 1 ) ( 2 ) }}</div>
|
||||
<div>{{ a [ b ] }}</div>
|
||||
<div>{{ [ 1 ] }}</div>
|
||||
<div>{{ { 'a' : 1 } }}</div>
|
||||
<div>{{ { a : 1 } }}</div>
|
||||
<div>{{ true }}</div>
|
||||
<div>{{ undefined }}</div>
|
||||
<div>{{ null }}</div>
|
||||
<div>{{ ( 1 ) }}</div>
|
||||
<div>{{ 1 }}</div>
|
||||
<div>{{ 'hello' }}</div>
|
||||
<div>{{ a ( 1 , 2 ) }}</div>
|
||||
<div>{{ a . b ( 1 , 2 ) }}</div>
|
||||
<div>{{ x ! }}</div>
|
||||
<div>{{ ! x }}</div>
|
||||
<div>{{ ( ( a ) ) }}</div>
|
||||
<div>{{ a }}</div>
|
||||
<div>{{ a // hello }}</div>
|
||||
<div>{{ a . b }}</div>
|
||||
<div>{{ a ?. b ( ) }}</div>
|
||||
<div>{{ a ?. b }}</div>
|
||||
<div>{{ a // hello }}</div>
|
||||
<label for="transmissionLayoutRadioButton">{{
|
||||
"SearchSelection.transmissionLayoutRadioButton" | localize:localizationSection
|
||||
}}</label>
|
||||
<label for="transmissionLayoutRadioButtontransmissionLayoutRadioButtontransmissionLayoutRadioButtontransmissionLayoutRadioButton">{{
|
||||
"SearchSelection.transmissionLayoutRadioButton" | localize:localizationSection
|
||||
}}</label>
|
||||
<label for="transmissionLayoutRadioButton">{{
|
||||
"SearchSelection.transmissionLayoutRadioButton" | localize:localizationSection
|
||||
}} </label>
|
||||
<label for="transmissionLayoutRadioButton"> {{
|
||||
"SearchSelection.transmissionLayoutRadioButton" | localize:localizationSection
|
||||
}}</label>
|
||||
<label for="transmissionLayoutRadioButton"> {{
|
||||
"SearchSelection.transmissionLayoutRadioButton" | localize:localizationSection
|
||||
}} </label>
|
||||
<div class="Nemo possimus non voluptates dicta accusamus id quia">{{copyTypes[options.copyType]}}</div>
|
||||
{{listRow.NextScheduledSendStatus == 1 || listRow.NextScheduledSendStatus == 2 || listRow.NextScheduledSendStatus == 3}}
|
||||
<span
|
||||
><!--
|
||||
--><span
|
||||
>{{a}}</span
|
||||
><!--
|
||||
--><span
|
||||
>{{b}}</span
|
||||
><!--
|
||||
--></span>
|
|
@ -0,0 +1,3 @@
|
|||
run_spec(__dirname, ["angular"]);
|
||||
run_spec(__dirname, ["angular"], { trailingComma: "es5" });
|
||||
run_spec(__dirname, ["angular"], { printWidth: 1 });
|
|
@ -0,0 +1,718 @@
|
|||
<!-- copied from: https://stackblitz.com/angular/ymdjlgmlavo -->
|
||||
|
||||
<a id="toc"></a>
|
||||
<h1>Template Syntax</h1>
|
||||
<a href="#interpolation">Interpolation</a><br>
|
||||
<a href="#expression-context">Expression context</a><br>
|
||||
<a href="#statement-context">Statement context</a><br>
|
||||
<a href="#mental-model">Mental Model</a><br>
|
||||
<a href="#buttons">Buttons</a><br>
|
||||
<a href="#prop-vs-attrib">Properties vs. Attributes</a><br>
|
||||
<br>
|
||||
<a href="#property-binding">Property Binding</a><br>
|
||||
<div style="margin-left:8px">
|
||||
<a href="#attribute-binding">Attribute Binding</a><br>
|
||||
<a href="#class-binding">Class Binding</a><br>
|
||||
<a href="#style-binding">Style Binding</a><br>
|
||||
</div>
|
||||
<br>
|
||||
<a href="#event-binding">Event Binding</a><br>
|
||||
<a href="#two-way">Two-way Binding</a><br>
|
||||
<br>
|
||||
<div>Directives</div>
|
||||
<div style="margin-left:8px">
|
||||
<a href="#ngModel">NgModel (two-way) Binding</a><br>
|
||||
<a href="#ngClass">NgClass Binding</a><br>
|
||||
<a href="#ngStyle">NgStyle Binding</a><br>
|
||||
<a href="#ngIf">NgIf</a><br>
|
||||
<a href="#ngFor">NgFor</a><br>
|
||||
<div style="margin-left:8px">
|
||||
<a href="#ngFor-index">NgFor with index</a><br>
|
||||
<a href="#ngFor-trackBy">NgFor with trackBy</a><br>
|
||||
</div>
|
||||
<a href="#ngSwitch">NgSwitch</a><br>
|
||||
</div>
|
||||
<br>
|
||||
<a href="#ref-vars">Template reference variables</a><br>
|
||||
<a href="#inputs-and-outputs">Inputs and outputs</a><br>
|
||||
<a href="#pipes">Pipes</a><br>
|
||||
<a href="#safe-navigation-operator">Safe navigation operator <i>?.</i></a><br>
|
||||
<a href="#non-null-assertion-operator">Non-null assertion operator <i>!.</i></a><br>
|
||||
<a href="#enums">Enums</a><br>
|
||||
|
||||
<!-- Interpolation and expressions -->
|
||||
<hr><h2 id="interpolation">Interpolation</h2>
|
||||
|
||||
<p>My current hero is {{currentHero.name}}</p>
|
||||
|
||||
<h3>
|
||||
{{title}}
|
||||
<img src="{{heroImageUrl}}" style="height:30px">
|
||||
</h3>
|
||||
|
||||
<!-- "The sum of 1 + 1 is 2" -->
|
||||
<p>The sum of 1 + 1 is {{1 + 1}}</p>
|
||||
|
||||
<!-- "The sum of 1 + 1 is not 4" -->
|
||||
<p>The sum of 1 + 1 is not {{1 + 1 + getVal()}}</p>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<hr><h2 id="expression-context">Expression context</h2>
|
||||
|
||||
<p>Component expression context ({{title}}, [hidden]="isUnchanged")</p>
|
||||
<div class="context">
|
||||
{{title}}
|
||||
<span [hidden]="isUnchanged">changed</span>
|
||||
</div>
|
||||
|
||||
|
||||
<p>Template input variable expression context (let hero)</p>
|
||||
<!-- template hides the following; plenty of examples later -->
|
||||
<ng-template>
|
||||
<div *ngFor="let hero of heroes">{{hero.name}}</div>
|
||||
</ng-template>
|
||||
|
||||
<p>Template reference variable expression context (#heroInput)</p>
|
||||
<div (keyup)="0" class="context">
|
||||
Type something:
|
||||
<input #heroInput> {{heroInput.value}}
|
||||
</div>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<hr><h2 id="statement-context">Statement context</h2>
|
||||
|
||||
<p>Component statement context ( (click)="onSave() )
|
||||
<div class="context">
|
||||
<button (click)="deleteHero()">Delete hero</button>
|
||||
</div>
|
||||
|
||||
<p>Template $event statement context</p>
|
||||
<div class="context">
|
||||
<button (click)="onSave($event)">Save</button>
|
||||
</div>
|
||||
|
||||
<p>Template input variable statement context (let hero)</p>
|
||||
<!-- template hides the following; plenty of examples later -->
|
||||
<div class="context">
|
||||
<button *ngFor="let hero of heroes" (click)="deleteHero(hero)">{{hero.name}}</button>
|
||||
</div>
|
||||
|
||||
<p>Template reference variable statement context (#heroForm)</p>
|
||||
<div class="context">
|
||||
<form #heroForm (ngSubmit)="onSubmit(heroForm)"> ... </form>
|
||||
</div>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<!-- New Mental Model -->
|
||||
<hr><h2 id="mental-model">New Mental Model</h2>
|
||||
|
||||
<!--<img src="http://www.wpclipart.com/cartoon/people/hero/hero_silhoutte_T.png">-->
|
||||
<!-- Public Domain terms of use: http://www.wpclipart.com/terms.html -->
|
||||
<div class="special">Mental Model</div>
|
||||
<img src="assets/images/hero.png">
|
||||
<button disabled>Save</button>
|
||||
<br><br>
|
||||
|
||||
<div>
|
||||
<!-- Normal HTML -->
|
||||
<div class="special">Mental Model</div>
|
||||
<!-- Wow! A new element! -->
|
||||
<app-hero-detail></app-hero-detail>
|
||||
</div>
|
||||
<br><br>
|
||||
|
||||
<div>
|
||||
<!-- Bind button disabled state to `isUnchanged` property -->
|
||||
<button [disabled]="isUnchanged">Save</button>
|
||||
</div>
|
||||
<br><br>
|
||||
|
||||
<div>
|
||||
<img [src]="heroImageUrl">
|
||||
<app-hero-detail [hero]="currentHero"></app-hero-detail>
|
||||
<div [ngClass]="{'special': isSpecial}"></div>
|
||||
</div>
|
||||
<br><br>
|
||||
|
||||
<button (click)="onSave()">Save</button>
|
||||
<app-hero-detail (deleteRequest)="deleteHero()"></app-hero-detail>
|
||||
<div (myClick)="clicked=$event" clickable>click me</div>
|
||||
{{clicked}}
|
||||
<br><br>
|
||||
|
||||
<div>
|
||||
Hero Name:
|
||||
<input [(ngModel)]="name">
|
||||
{{name}}
|
||||
</div>
|
||||
<br><br>
|
||||
|
||||
<button [attr.aria-label]="help">help</button>
|
||||
<br><br>
|
||||
|
||||
<div [class.special]="isSpecial">Special</div>
|
||||
<br><br>
|
||||
|
||||
<button [style.color]="isSpecial ? 'red' : 'green'">
|
||||
button</button>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<!-- property vs. attribute -->
|
||||
<hr><h2 id="prop-vs-attrib">Property vs. Attribute (img examples)</h2>
|
||||
<!-- examine the following <img> tag in the browser tools -->
|
||||
<img src="images/ng-logo.png"
|
||||
[src]="heroImageUrl">
|
||||
|
||||
<br><br>
|
||||
|
||||
<img [src]="iconUrl"/>
|
||||
<img bind-src="heroImageUrl"/>
|
||||
<img [attr.src]="villainImageUrl"/>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<!-- buttons -->
|
||||
<hr><h2 id="buttons">Buttons</h2>
|
||||
|
||||
<button>Enabled (but does nothing)</button>
|
||||
<button disabled>Disabled</button>
|
||||
<button disabled=false>Still disabled</button>
|
||||
<br><br>
|
||||
<button disabled>disabled by attribute</button>
|
||||
<button [disabled]="isUnchanged">disabled by property binding</button>
|
||||
<br><br>
|
||||
<button bind-disabled="isUnchanged" on-click="onSave($event)">Disabled Cancel</button>
|
||||
<button [disabled]="!canSave" (click)="onSave($event)">Enabled Save</button>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<!-- property binding -->
|
||||
<hr><h2 id="property-binding">Property Binding</h2>
|
||||
|
||||
<img [src]="heroImageUrl">
|
||||
<button [disabled]="isUnchanged">Cancel is disabled</button>
|
||||
<div [ngClass]="classes">[ngClass] binding to the classes property</div>
|
||||
<app-hero-detail [hero]="currentHero"></app-hero-detail>
|
||||
<img bind-src="heroImageUrl">
|
||||
|
||||
<!-- ERROR: HeroDetailComponent.hero expects a
|
||||
Hero object, not the string "currentHero" -->
|
||||
<div *ngIf="false">
|
||||
<app-hero-detail hero="currentHero"></app-hero-detail>
|
||||
</div>
|
||||
<app-hero-detail prefix="You are my" [hero]="currentHero"></app-hero-detail>
|
||||
|
||||
<p><img src="{{heroImageUrl}}"> is the <i>interpolated</i> image.</p>
|
||||
<p><img [src]="heroImageUrl"> is the <i>property bound</i> image.</p>
|
||||
|
||||
<p><span>"{{title}}" is the <i>interpolated</i> title.</span></p>
|
||||
<p>"<span [innerHTML]="title"></span>" is the <i>property bound</i> title.</p>
|
||||
|
||||
<!--
|
||||
Angular generates warnings for these two lines as it sanitizes them
|
||||
WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss).
|
||||
-->
|
||||
<p><span>"{{evilTitle}}" is the <i>interpolated</i> evil title.</span></p>
|
||||
<p>"<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil title.</p>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<!-- attribute binding -->
|
||||
<hr><h2 id="attribute-binding">Attribute Binding</h2>
|
||||
|
||||
<!-- create and set a colspan attribute -->
|
||||
<table border=1>
|
||||
<!-- expression calculates colspan=2 -->
|
||||
<tr><td [attr.colspan]="1 + 1">One-Two</td></tr>
|
||||
|
||||
<!-- ERROR: There is no `colspan` property to set!
|
||||
<tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
|
||||
-->
|
||||
|
||||
<tr><td>Five</td><td>Six</td></tr>
|
||||
</table>
|
||||
|
||||
<br>
|
||||
<!-- create and set an aria attribute for assistive technology -->
|
||||
<button [attr.aria-label]="actionName">{{actionName}} with Aria</button>
|
||||
<br><br>
|
||||
|
||||
<!-- The following effects are not discussed in the chapter -->
|
||||
<div>
|
||||
<!-- any use of [attr.disabled] creates the disabled attribute -->
|
||||
<button [attr.disabled]="isUnchanged">Disabled</button>
|
||||
|
||||
<button [attr.disabled]="!isUnchanged">Disabled as well</button>
|
||||
|
||||
<!-- we'd have to remove it with property binding -->
|
||||
<button disabled [disabled]="false">Enabled (but inert)</button>
|
||||
</div>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<!-- class binding -->
|
||||
<hr><h2 id="class-binding">Class Binding</h2>
|
||||
|
||||
<!-- standard class attribute setting -->
|
||||
<div class="bad curly special">Bad curly special</div>
|
||||
|
||||
<!-- reset/override all class names with a binding -->
|
||||
<div class="bad curly special"
|
||||
[class]="badCurly">Bad curly</div>
|
||||
|
||||
<!-- toggle the "special" class on/off with a property -->
|
||||
<div [class.special]="isSpecial">The class binding is special</div>
|
||||
|
||||
<!-- binding to `class.special` trumps the class attribute -->
|
||||
<div class="special"
|
||||
[class.special]="!isSpecial">This one is not so special</div>
|
||||
|
||||
<div bind-class.special="isSpecial">This class binding is special too</div>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<!--style binding -->
|
||||
<hr><h2 id="style-binding">Style Binding</h2>
|
||||
|
||||
<button [style.color]="isSpecial ? 'red': 'green'">Red</button>
|
||||
<button [style.background-color]="canSave ? 'cyan': 'grey'" >Save</button>
|
||||
|
||||
<button [style.font-size.em]="isSpecial ? 3 : 1" >Big</button>
|
||||
<button [style.font-size.%]="!isSpecial ? 150 : 50" >Small</button>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<!-- event binding -->
|
||||
<hr><h2 id="event-binding">Event Binding</h2>
|
||||
|
||||
<button (click)="onSave()">Save</button>
|
||||
|
||||
<button on-click="onSave()">On Save</button>
|
||||
|
||||
<div>
|
||||
<!-- `myClick` is an event on the custom `ClickDirective` -->
|
||||
<div (myClick)="clickMessage=$event" clickable>click with myClick</div>
|
||||
{{clickMessage}}
|
||||
</div>
|
||||
|
||||
|
||||
<!-- binding to a nested component -->
|
||||
<app-hero-detail (deleteRequest)="deleteHero($event)" [hero]="currentHero"></app-hero-detail>
|
||||
<br>
|
||||
|
||||
<app-big-hero-detail
|
||||
(deleteRequest)="deleteHero($event)"
|
||||
[hero]="currentHero">
|
||||
</app-big-hero-detail>
|
||||
|
||||
<div class="parent-div" (click)="onClickMe($event)" clickable>Click me
|
||||
<div class="child-div">Click me too!</div>
|
||||
</div>
|
||||
|
||||
<!-- Will save only once -->
|
||||
<div (click)="onSave()" clickable>
|
||||
<button (click)="onSave($event)">Save, no propagation</button>
|
||||
</div>
|
||||
|
||||
<!-- Will save twice -->
|
||||
<div (click)="onSave()" clickable>
|
||||
<button (click)="onSave()">Save w/ propagation</button>
|
||||
</div>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<hr><h2 id="two-way">Two-way Binding</h2>
|
||||
<div id="two-way-1">
|
||||
<app-sizer [(size)]="fontSizePx"></app-sizer>
|
||||
<div [style.font-size.px]="fontSizePx">Resizable Text</div>
|
||||
<label>FontSize (px): <input [(ngModel)]="fontSizePx"></label>
|
||||
</div>
|
||||
<br>
|
||||
<div id="two-way-2">
|
||||
<h3>De-sugared two-way binding</h3>
|
||||
<app-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></app-sizer>
|
||||
</div>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<!-- Two way data binding unwound;
|
||||
passing the changed display value to the event handler via `$event` -->
|
||||
<hr><h2 id="ngModel">NgModel (two-way) Binding</h2>
|
||||
|
||||
<h3>Result: {{currentHero.name}}</h3>
|
||||
|
||||
<input [value]="currentHero.name"
|
||||
(input)="currentHero.name=$event.target.value" >
|
||||
without NgModel
|
||||
<br>
|
||||
<input [(ngModel)]="currentHero.name">
|
||||
[(ngModel)]
|
||||
<br>
|
||||
<input bindon-ngModel="currentHero.name">
|
||||
bindon-ngModel
|
||||
<br>
|
||||
<input
|
||||
[ngModel]="currentHero.name"
|
||||
(ngModelChange)="currentHero.name=$event">
|
||||
(ngModelChange)="...name=$event"
|
||||
<br>
|
||||
<input
|
||||
[ngModel]="currentHero.name"
|
||||
(ngModelChange)="setUppercaseName($event)">
|
||||
(ngModelChange)="setUppercaseName($event)"
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<!-- NgClass binding -->
|
||||
<hr><h2 id="ngClass">NgClass Binding</h2>
|
||||
|
||||
<p>currentClasses is {{currentClasses | json}}</p>
|
||||
<div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special</div>
|
||||
|
||||
<!-- not used in chapter -->
|
||||
<br>
|
||||
<label>saveable <input type="checkbox" [(ngModel)]="canSave"></label> |
|
||||
<label>modified: <input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged"></label> |
|
||||
<label>special: <input type="checkbox" [(ngModel)]="isSpecial"></label>
|
||||
<button (click)="setCurrentClasses()">Refresh currentClasses</button>
|
||||
<br><br>
|
||||
<div [ngClass]="currentClasses">
|
||||
This div should be {{ canSave ? "": "not"}} saveable,
|
||||
{{ isUnchanged ? "unchanged" : "modified" }} and,
|
||||
{{ isSpecial ? "": "not"}} special after clicking "Refresh".</div>
|
||||
<br><br>
|
||||
|
||||
<div [ngClass]="isSpecial ? 'special' : ''">This div is special</div>
|
||||
|
||||
<div class="bad curly special">Bad curly special</div>
|
||||
<div [ngClass]="{'bad':false, 'curly':true, 'special':true}">Curly special</div>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<!-- NgStyle binding -->
|
||||
<hr><h2 id="ngStyle">NgStyle Binding</h2>
|
||||
|
||||
<div [style.font-size]="isSpecial ? 'x-large' : 'smaller'" >
|
||||
This div is x-large or smaller.
|
||||
</div>
|
||||
|
||||
<h3>[ngStyle] binding to currentStyles - CSS property names</h3>
|
||||
<p>currentStyles is {{currentStyles | json}}</p>
|
||||
<div [ngStyle]="currentStyles">
|
||||
This div is initially italic, normal weight, and extra large (24px).
|
||||
</div>
|
||||
|
||||
<!-- not used in chapter -->
|
||||
<br>
|
||||
<label>italic: <input type="checkbox" [(ngModel)]="canSave"></label> |
|
||||
<label>normal: <input type="checkbox" [(ngModel)]="isUnchanged"></label> |
|
||||
<label>xlarge: <input type="checkbox" [(ngModel)]="isSpecial"></label>
|
||||
<button (click)="setCurrentStyles()">Refresh currentStyles</button>
|
||||
<br><br>
|
||||
<div [ngStyle]="currentStyles">
|
||||
This div should be {{ canSave ? "italic": "plain"}},
|
||||
{{ isUnchanged ? "normal weight" : "bold" }} and,
|
||||
{{ isSpecial ? "extra large": "normal size"}} after clicking "Refresh".</div>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<!-- NgIf binding -->
|
||||
<hr><h2 id="ngIf">NgIf Binding</h2>
|
||||
|
||||
<app-hero-detail *ngIf="isActive"></app-hero-detail>
|
||||
|
||||
<div *ngIf="currentHero">Hello, {{currentHero.name}}</div>
|
||||
<div *ngIf="nullHero">Hello, {{nullHero.name}}</div>
|
||||
|
||||
<!-- NgIf binding with template (no *) -->
|
||||
|
||||
<ng-template [ngIf]="currentHero">Add {{currentHero.name}} with template</ng-template>
|
||||
|
||||
<!-- Does not show because isActive is false! -->
|
||||
<div>Hero Detail removed from DOM (via template) because isActive is false</div>
|
||||
<ng-template [ngIf]="isActive">
|
||||
<app-hero-detail></app-hero-detail>
|
||||
</ng-template>
|
||||
|
||||
<!-- isSpecial is true -->
|
||||
<div [class.hidden]="!isSpecial">Show with class</div>
|
||||
<div [class.hidden]="isSpecial">Hide with class</div>
|
||||
|
||||
<!-- HeroDetail is in the DOM but hidden -->
|
||||
<app-hero-detail [class.hidden]="isSpecial"></app-hero-detail>
|
||||
|
||||
<div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div>
|
||||
<div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<!-- NgFor binding -->
|
||||
<hr><h2 id="ngFor">NgFor Binding</h2>
|
||||
|
||||
<div class="box">
|
||||
<div *ngFor="let hero of heroes">{{hero.name}}</div>
|
||||
</div>
|
||||
<br>
|
||||
|
||||
<div class="box">
|
||||
<!-- *ngFor w/ hero-detail Component -->
|
||||
<app-hero-detail *ngFor="let hero of heroes" [hero]="hero"></app-hero-detail>
|
||||
</div>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<h4 id="ngFor-index">*ngFor with index</h4>
|
||||
<p>with <i>semi-colon</i> separator</p>
|
||||
<div class="box">
|
||||
<div *ngFor="let hero of heroes; let i=index">{{i + 1}} - {{hero.name}}</div>
|
||||
</div>
|
||||
|
||||
<p>with <i>comma</i> separator</p>
|
||||
<div class="box">
|
||||
<!-- Ex: "1 - Hercules" -->
|
||||
<div *ngFor="let hero of heroes, let i=index">{{i + 1}} - {{hero.name}}</div>
|
||||
</div>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<h4 id="ngFor-trackBy">*ngFor trackBy</h4>
|
||||
<button (click)="resetHeroes()">Reset heroes</button>
|
||||
<button (click)="changeIds()">Change ids</button>
|
||||
<button (click)="clearTrackByCounts()">Clear counts</button>
|
||||
|
||||
<p><i>without</i> trackBy</p>
|
||||
<div class="box">
|
||||
<div #noTrackBy *ngFor="let hero of heroes">({{hero.id}}) {{hero.name}}</div>
|
||||
|
||||
<div id="noTrackByCnt" *ngIf="heroesNoTrackByCount" >
|
||||
Hero DOM elements change #{{heroesNoTrackByCount}} without trackBy
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>with trackBy</p>
|
||||
<div class="box">
|
||||
<div #withTrackBy *ngFor="let hero of heroes; trackBy: trackByHeroes">({{hero.id}}) {{hero.name}}</div>
|
||||
|
||||
<div id="withTrackByCnt" *ngIf="heroesWithTrackByCount">
|
||||
Hero DOM elements change #{{heroesWithTrackByCount}} with trackBy
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br><br><br>
|
||||
|
||||
<p>with trackBy and <i>semi-colon</i> separator</p>
|
||||
<div class="box">
|
||||
<div *ngFor="let hero of heroes; trackBy: trackByHeroes">
|
||||
({{hero.id}}) {{hero.name}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>with trackBy and <i>comma</i> separator</p>
|
||||
<div class="box">
|
||||
<div *ngFor="let hero of heroes, trackBy: trackByHeroes">({{hero.id}}) {{hero.name}}</div>
|
||||
</div>
|
||||
|
||||
<p>with trackBy and <i>space</i> separator</p>
|
||||
<div class="box">
|
||||
<div *ngFor="let hero of heroes trackBy: trackByHeroes">({{hero.id}}) {{hero.name}}</div>
|
||||
</div>
|
||||
|
||||
<p>with <i>generic</i> trackById function</p>
|
||||
<div class="box">
|
||||
<div *ngFor="let hero of heroes, trackBy: trackById">({{hero.id}}) {{hero.name}}</div>
|
||||
</div>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<!-- NgSwitch binding -->
|
||||
<hr><h2 id="ngSwitch">NgSwitch Binding</h2>
|
||||
|
||||
<p>Pick your favorite hero</p>
|
||||
<div>
|
||||
<label *ngFor="let h of heroes">
|
||||
<input type="radio" name="heroes" [(ngModel)]="currentHero" [value]="h">{{h.name}}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div [ngSwitch]="currentHero.emotion">
|
||||
<app-happy-hero *ngSwitchCase="'happy'" [hero]="currentHero"></app-happy-hero>
|
||||
<app-sad-hero *ngSwitchCase="'sad'" [hero]="currentHero"></app-sad-hero>
|
||||
<app-confused-hero *ngSwitchCase="'confused'" [hero]="currentHero"></app-confused-hero>
|
||||
<div *ngSwitchCase="'confused'">Are you as confused as {{currentHero.name}}?</div>
|
||||
<app-unknown-hero *ngSwitchDefault [hero]="currentHero"></app-unknown-hero>
|
||||
</div>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<!-- template reference variable -->
|
||||
<hr><h2 id="ref-vars">Template reference variables</h2>
|
||||
|
||||
<input #phone placeholder="phone number">
|
||||
|
||||
<!-- lots of other elements -->
|
||||
|
||||
<!-- phone refers to the input element; pass its `value` to an event handler -->
|
||||
<button (click)="callPhone(phone.value)">Call</button>
|
||||
|
||||
<input ref-fax placeholder="fax number">
|
||||
<button (click)="callFax(fax.value)">Fax</button>
|
||||
|
||||
<!-- btn refers to the button element; show its disabled state -->
|
||||
<button #btn disabled [innerHTML]="'disabled by attribute: '+btn.disabled"></button>
|
||||
|
||||
<h4>Example Form</h4>
|
||||
<app-hero-form [hero]="currentHero"></app-hero-form>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<!-- inputs and output -->
|
||||
<hr><h2 id="inputs-and-outputs">Inputs and Outputs</h2>
|
||||
|
||||
<img [src]="iconUrl"/>
|
||||
<button (click)="onSave()">Save</button>
|
||||
|
||||
<app-hero-detail [hero]="currentHero" (deleteRequest)="deleteHero($event)">
|
||||
</app-hero-detail>
|
||||
|
||||
<div (myClick)="clickMessage2=$event" clickable>myClick2</div>
|
||||
{{clickMessage2}}
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<!-- Pipes -->
|
||||
<hr><h2 id="pipes">Pipes</h2>
|
||||
|
||||
<div>Title through uppercase pipe: {{title | uppercase}}</div>
|
||||
|
||||
<!-- Pipe chaining: convert title to uppercase, then to lowercase -->
|
||||
<div>
|
||||
Title through a pipe chain:
|
||||
{{title | uppercase | lowercase}}
|
||||
</div>
|
||||
|
||||
<!-- pipe with configuration argument => "February 25, 1970" -->
|
||||
<div>Birthdate: {{currentHero?.birthdate | date:'longDate'}}</div>
|
||||
|
||||
<div>{{currentHero | json}}</div>
|
||||
|
||||
<div>Birthdate: {{(currentHero?.birthdate | date:'longDate') | uppercase}}</div>
|
||||
|
||||
<div>
|
||||
<!-- pipe price to USD and display the $ symbol -->
|
||||
<label>Price: </label>{{product.price | currency:'USD':true}}
|
||||
</div>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<!-- Null values and the safe navigation operator -->
|
||||
<hr><h2 id="safe-navigation-operator">Safe navigation operator <i>?.</i></h2>
|
||||
|
||||
<div>
|
||||
The title is {{title}}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
The current hero's name is {{currentHero?.name}}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
The current hero's name is {{currentHero.name}}
|
||||
</div>
|
||||
|
||||
|
||||
<!--
|
||||
The null hero's name is {{nullHero.name}}
|
||||
|
||||
See console log:
|
||||
TypeError: Cannot read property 'name' of null in [null]
|
||||
-->
|
||||
|
||||
<!--No hero, div not displayed, no error -->
|
||||
<div *ngIf="nullHero">The null hero's name is {{nullHero.name}}</div>
|
||||
|
||||
<div>
|
||||
The null hero's name is {{nullHero && nullHero.name}}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<!-- No hero, no problem! -->
|
||||
The null hero's name is {{nullHero?.name}}
|
||||
</div>
|
||||
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<!-- non-null assertion operator -->
|
||||
<hr><h2 id="non-null-assertion-operator">Non-null assertion operator <i>!.</i></h2>
|
||||
|
||||
<div>
|
||||
<!--No hero, no text -->
|
||||
<div *ngIf="hero">
|
||||
The hero's name is {{hero!.name}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<!-- non-null assertion operator -->
|
||||
<hr><h2 id="any-type-cast-function">$any type cast function <i>$any( )</i>.</h2>
|
||||
|
||||
<div>
|
||||
<!-- Accessing an undeclared member -->
|
||||
<div>
|
||||
The hero's marker is {{$any(hero).marker}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<!-- Accessing an undeclared member -->
|
||||
<div>
|
||||
Undeclared members is {{$any(this).member}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<!-- TODO: discuss this in the Style binding section -->
|
||||
<!-- enums in bindings -->
|
||||
<hr><h2 id="enums">Enums in binding</h2>
|
||||
|
||||
<p>
|
||||
The name of the Color.Red enum is {{Color[Color.Red]}}.<br>
|
||||
The current color is {{Color[color]}} and its number is {{color}}.<br>
|
||||
<button [style.color]="Color[color]" (click)="colorToggle()">Enum Toggle</button>
|
||||
</p>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
|
||||
<!--
|
||||
Copyright 2017-2018 Google Inc. All Rights Reserved.
|
||||
Use of this source code is governed by an MIT-style license that
|
||||
can be found in the LICENSE file at http://angular.io/license
|
||||
-->
|
||||
|
||||
<div id="heroForm">
|
||||
<form (ngSubmit)="onSubmit(heroForm)" #heroForm="ngForm">
|
||||
<div class="form-group">
|
||||
<label for="name">Name
|
||||
<input class="form-control" name="name" required [(ngModel)]="hero.name">
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit" [disabled]="!heroForm.form.valid">Submit</button>
|
||||
</form>
|
||||
<div [hidden]="!heroForm.form.valid">
|
||||
{{submitMessage}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!--
|
||||
Copyright 2017-2018 Google Inc. All Rights Reserved.
|
||||
Use of this source code is governed by an MIT-style license that
|
||||
can be found in the LICENSE file at http://angular.io/license
|
||||
-->
|
|
@ -175,33 +175,33 @@ exports[`boolean.html - html-verify 1`] = `
|
|||
<button type="submit" disabled>This is valid.</button>
|
||||
<button type="submit" disabled="">This is valid.</button>
|
||||
<button type="submit" disabled="disabled">This is valid.</button>
|
||||
<button type="submit" disabled="true"
|
||||
>This is valid. This will be disabled.</button
|
||||
>
|
||||
<button type="submit" disabled="true"
|
||||
>This is valid. This will be disabled.</button
|
||||
>
|
||||
<button type="submit" disabled="true"
|
||||
>This is valid. This will be disabled.</button
|
||||
>
|
||||
<button type="submit" disabled="false"
|
||||
>This is valid. This will be disabled.</button
|
||||
>
|
||||
<button type="submit" disabled="false"
|
||||
>This is valid. This will be disabled.</button
|
||||
>
|
||||
<button type="submit" disabled="false"
|
||||
>This is valid. This will be disabled.</button
|
||||
>
|
||||
<button type="submit" disabled="hahah"
|
||||
>This is valid. This will be disabled.</button
|
||||
>
|
||||
<button type="submit" disabled="hahah"
|
||||
>This is valid. This will be disabled.</button
|
||||
>
|
||||
<button type="submit" disabled="hahah"
|
||||
>This is valid. This will be disabled.</button
|
||||
>
|
||||
<button type="submit" disabled="true">
|
||||
This is valid. This will be disabled.
|
||||
</button>
|
||||
<button type="submit" disabled="true">
|
||||
This is valid. This will be disabled.
|
||||
</button>
|
||||
<button type="submit" disabled="true">
|
||||
This is valid. This will be disabled.
|
||||
</button>
|
||||
<button type="submit" disabled="false">
|
||||
This is valid. This will be disabled.
|
||||
</button>
|
||||
<button type="submit" disabled="false">
|
||||
This is valid. This will be disabled.
|
||||
</button>
|
||||
<button type="submit" disabled="false">
|
||||
This is valid. This will be disabled.
|
||||
</button>
|
||||
<button type="submit" disabled="hahah">
|
||||
This is valid. This will be disabled.
|
||||
</button>
|
||||
<button type="submit" disabled="hahah">
|
||||
This is valid. This will be disabled.
|
||||
</button>
|
||||
<button type="submit" disabled="hahah">
|
||||
This is valid. This will be disabled.
|
||||
</button>
|
||||
<input type="checkbox" checked disabled name="cheese" />
|
||||
<input type="checkbox" checked="checked" disabled="disabled" name="cheese" />
|
||||
<input type="checkbox" checked="" disabled="" name="cheese" />
|
||||
|
@ -237,6 +237,48 @@ exports[`single-quotes.html - html-verify 1`] = `
|
|||
|
||||
`;
|
||||
|
||||
exports[`srcset.html - html-verify 1`] = `
|
||||
<img src="/assets/visual.png"
|
||||
srcset="/assets/visual@0.5.png 400w, /assets/visual.png 805w"
|
||||
sizes="(max-width: 66rem) 100vw, 66rem" alt=""/>
|
||||
<img src="/assets/visual.png"
|
||||
srcset="/assets/visual@0.5.png 400w, /assets/visual.png 805w, /assets/visual@2x.png 1610w, /assets/visual@3x.png 2415w"
|
||||
sizes="(max-width: 66rem) 100vw, 66rem" alt=""/>
|
||||
<img src="/assets/visual.png"
|
||||
srcset="/assets/visual@0.5.png 0.5x, /assets/visual.png 1111x, /assets/visual@2x.png 2x, /assets/visual@3x.png 3.3333x"
|
||||
sizes="(max-width: 66rem) 100vw, 66rem" alt=""/>
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
<img
|
||||
src="/assets/visual.png"
|
||||
srcset="/assets/visual@0.5.png 400w, /assets/visual.png 805w"
|
||||
sizes="(max-width: 66rem) 100vw, 66rem"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
src="/assets/visual.png"
|
||||
srcset="
|
||||
/assets/visual@0.5.png 400w,
|
||||
/assets/visual.png 805w,
|
||||
/assets/visual@2x.png 1610w,
|
||||
/assets/visual@3x.png 2415w
|
||||
"
|
||||
sizes="(max-width: 66rem) 100vw, 66rem"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
src="/assets/visual.png"
|
||||
srcset="
|
||||
/assets/visual@0.5.png 0.5x,
|
||||
/assets/visual.png 1111x,
|
||||
/assets/visual@2x.png 2x,
|
||||
/assets/visual@3x.png 3.3333x
|
||||
"
|
||||
sizes="(max-width: 66rem) 100vw, 66rem"
|
||||
alt=""
|
||||
/>
|
||||
|
||||
`;
|
||||
|
||||
exports[`without-quotes.html - html-verify 1`] = `
|
||||
<p title=Title>String</p>
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<img src="/assets/visual.png"
|
||||
srcset="/assets/visual@0.5.png 400w, /assets/visual.png 805w"
|
||||
sizes="(max-width: 66rem) 100vw, 66rem" alt=""/>
|
||||
<img src="/assets/visual.png"
|
||||
srcset="/assets/visual@0.5.png 400w, /assets/visual.png 805w, /assets/visual@2x.png 1610w, /assets/visual@3x.png 2415w"
|
||||
sizes="(max-width: 66rem) 100vw, 66rem" alt=""/>
|
||||
<img src="/assets/visual.png"
|
||||
srcset="/assets/visual@0.5.png 0.5x, /assets/visual.png 1111x, /assets/visual@2x.png 2x, /assets/visual@3x.png 3.3333x"
|
||||
sizes="(max-width: 66rem) 100vw, 66rem" alt=""/>
|
|
@ -260,17 +260,6 @@ exports[`html-comments.html - html-verify 1`] = `
|
|||
|
||||
`;
|
||||
|
||||
exports[`html-fragment.html - html-verify 1`] = `
|
||||
<a href="#">Link</a>
|
||||
|
||||
<textarea>
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
<a href="#">Link</a>
|
||||
|
||||
<textarea></textarea>
|
||||
|
||||
`;
|
||||
|
||||
exports[`html5-boilerplate.html - html-verify 1`] = `
|
||||
<!doctype html>
|
||||
<html class="no-js" lang="">
|
||||
|
@ -352,7 +341,9 @@ exports[`html5-boilerplate.html - html-verify 1`] = `
|
|||
></script>
|
||||
<script>
|
||||
window.jQuery ||
|
||||
document.write('<script src="js/vendor/jquery-3.3.1.min.js"></script>');
|
||||
document.write(
|
||||
'<script src="js/vendor/jquery-3.3.1.min.js"><\\/script>'
|
||||
);
|
||||
</script>
|
||||
<script src="js/plugins.js"></script>
|
||||
<script src="js/main.js"></script>
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
<a href="#">Link</a>
|
||||
|
||||
<textarea>
|
|
@ -0,0 +1,8 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`example.html - html-verify 1`] = `
|
||||
<span><![CDATA[<sender>John Smith</sender>]]></span>
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
<span><![CDATA[<sender>John Smith</sender>]]></span>
|
||||
|
||||
`;
|
|
@ -0,0 +1 @@
|
|||
<span><![CDATA[<sender>John Smith</sender>]]></span>
|
|
@ -0,0 +1 @@
|
|||
run_spec(__dirname, ["html"]);
|
|
@ -35,6 +35,28 @@ exports[`before-text.html - html-verify 3`] = `
|
|||
|
||||
`;
|
||||
|
||||
exports[`before-text.html - html-verify 4`] = `
|
||||
<!-- hello -->
|
||||
|
||||
123
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
<!-- hello -->
|
||||
|
||||
123
|
||||
|
||||
`;
|
||||
|
||||
exports[`before-text.html - html-verify 5`] = `
|
||||
<!-- hello -->
|
||||
|
||||
123
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
<!-- hello -->
|
||||
|
||||
123
|
||||
|
||||
`;
|
||||
|
||||
exports[`conditional.html - html-verify 1`] = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
@ -141,6 +163,75 @@ exports[`conditional.html - html-verify 3`] = `
|
|||
|
||||
`;
|
||||
|
||||
exports[`conditional.html - html-verify 4`] = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<!--[if IE 5]>This is IE 5<br><![endif]-->
|
||||
<!--[if IE 6]>This is IE 6<br><![endif]-->
|
||||
<!--[if IE 7]>This is IE 7<br><![endif]-->
|
||||
<!--[if IE 8]>This is IE 8<br><![endif]-->
|
||||
<!--[if IE 9]>This is IE 9<br><![endif]-->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<!--[if IE 5]>This is IE 5<br /><![endif]-->
|
||||
<!--[if IE 6]>This is IE 6<br /><![endif]-->
|
||||
<!--[if IE 7]>This is IE 7<br /><![endif]-->
|
||||
<!--[if IE 8]>This is IE 8<br /><![endif]-->
|
||||
<!--[if IE 9]>This is IE 9<br /><![endif]-->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
`;
|
||||
|
||||
exports[`conditional.html - html-verify 5`] = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<!--[if IE 5]>This is IE 5<br><![endif]-->
|
||||
<!--[if IE 6]>This is IE 6<br><![endif]-->
|
||||
<!--[if IE 7]>This is IE 7<br><![endif]-->
|
||||
<!--[if IE 8]>This is IE 8<br><![endif]-->
|
||||
<!--[if IE 9]>This is IE 9<br><![endif]-->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<!--[if IE 5]>
|
||||
This is IE 5
|
||||
<br />
|
||||
<![endif]-->
|
||||
<!--[if IE 6]>
|
||||
This is IE 6
|
||||
<br />
|
||||
<![endif]-->
|
||||
<!--[if IE 7]>
|
||||
This is IE 7
|
||||
<br />
|
||||
<![endif]-->
|
||||
<!--[if IE 8]>
|
||||
This is IE 8
|
||||
<br />
|
||||
<![endif]-->
|
||||
<!--[if IE 9]>
|
||||
This is IE 9
|
||||
<br />
|
||||
<![endif]-->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
`;
|
||||
|
||||
exports[`for_debugging.html - html-verify 1`] = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
@ -267,6 +358,90 @@ exports[`for_debugging.html - html-verify 3`] = `
|
|||
|
||||
`;
|
||||
|
||||
exports[`for_debugging.html - html-verify 4`] = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<!-- Do not display this at the moment
|
||||
<img border="0" src="pic_trulli.jpg" alt="Trulli">
|
||||
-->
|
||||
|
||||
<!-- Do not display this at the moment
|
||||
<img border="0" src="pic_trulli.jpg" alt="Trulli">
|
||||
-->
|
||||
|
||||
<!-- Do not display this at the moment
|
||||
<img border="0" src="pic_trulli.jpg" alt="Trulli">
|
||||
-->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<!--
|
||||
Do not display this at the moment
|
||||
<img border="0" src="pic_trulli.jpg" alt="Trulli">
|
||||
-->
|
||||
|
||||
<!--
|
||||
Do not display this at the moment
|
||||
<img border="0" src="pic_trulli.jpg" alt="Trulli">
|
||||
-->
|
||||
|
||||
<!--
|
||||
Do not display this at the moment
|
||||
<img border="0" src="pic_trulli.jpg" alt="Trulli">
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
`;
|
||||
|
||||
exports[`for_debugging.html - html-verify 5`] = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<!-- Do not display this at the moment
|
||||
<img border="0" src="pic_trulli.jpg" alt="Trulli">
|
||||
-->
|
||||
|
||||
<!-- Do not display this at the moment
|
||||
<img border="0" src="pic_trulli.jpg" alt="Trulli">
|
||||
-->
|
||||
|
||||
<!-- Do not display this at the moment
|
||||
<img border="0" src="pic_trulli.jpg" alt="Trulli">
|
||||
-->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<!--
|
||||
Do not display this at the moment
|
||||
<img border="0" src="pic_trulli.jpg" alt="Trulli">
|
||||
-->
|
||||
|
||||
<!--
|
||||
Do not display this at the moment
|
||||
<img border="0" src="pic_trulli.jpg" alt="Trulli">
|
||||
-->
|
||||
|
||||
<!--
|
||||
Do not display this at the moment
|
||||
<img border="0" src="pic_trulli.jpg" alt="Trulli">
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
`;
|
||||
|
||||
exports[`hidden.html - html-verify 1`] = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
@ -369,6 +544,64 @@ exports[`hidden.html - html-verify 3`] = `
|
|||
|
||||
`;
|
||||
|
||||
exports[`hidden.html - html-verify 4`] = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<!--This is a comment-->
|
||||
<!-- This is a comment -->
|
||||
<!-- This is a comment -->
|
||||
<!-- This is a comment -->
|
||||
<p>This is a paragraph.</p>
|
||||
<!-- Comments are not displayed in the browser -->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<!-- This is a comment -->
|
||||
<!-- This is a comment -->
|
||||
<!-- This is a comment -->
|
||||
<!-- This is a comment -->
|
||||
<p>This is a paragraph.</p>
|
||||
<!-- Comments are not displayed in the browser -->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
`;
|
||||
|
||||
exports[`hidden.html - html-verify 5`] = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<!--This is a comment-->
|
||||
<!-- This is a comment -->
|
||||
<!-- This is a comment -->
|
||||
<!-- This is a comment -->
|
||||
<p>This is a paragraph.</p>
|
||||
<!-- Comments are not displayed in the browser -->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<!-- This is a comment -->
|
||||
<!-- This is a comment -->
|
||||
<!-- This is a comment -->
|
||||
<!-- This is a comment -->
|
||||
<p>This is a paragraph.</p>
|
||||
<!-- Comments are not displayed in the browser -->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
`;
|
||||
|
||||
exports[`surrounding-empty-line.html - html-verify 1`] = `
|
||||
<ul><!-- 123
|
||||
--><li>First</li><!-- 123
|
||||
|
@ -405,21 +638,44 @@ exports[`surrounding-empty-line.html - html-verify 1`] = `
|
|||
1 --><span>a</span><!--
|
||||
2 --><span>b</span><!--
|
||||
3 --></span>
|
||||
|
||||
123<!---->456
|
||||
|
||||
123<!--x-->456
|
||||
|
||||
<!-- A
|
||||
B -->
|
||||
|
||||
<!--
|
||||
The null hero's name is {{nullHero.name}}
|
||||
|
||||
See console log:
|
||||
TypeError: Cannot read property 'name' of null in [null]
|
||||
-->
|
||||
|
||||
<!--
|
||||
The null hero's name is {{nullHero.name}}
|
||||
|
||||
See console log:
|
||||
TypeError: Cannot read property 'name' of null in [null]
|
||||
-->
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
<ul>
|
||||
<!-- 123
|
||||
--><li>First</li
|
||||
><!--
|
||||
<!-- 123 -->
|
||||
<li>First</li>
|
||||
<!--
|
||||
123
|
||||
456
|
||||
789
|
||||
--><li>Second</li
|
||||
><!--
|
||||
-->
|
||||
<li>Second</li>
|
||||
<!--
|
||||
123
|
||||
456
|
||||
789
|
||||
--><li>Second</li
|
||||
><!--
|
||||
-->
|
||||
<li>Second</li>
|
||||
<!--
|
||||
123
|
||||
456
|
||||
789
|
||||
|
@ -449,6 +705,30 @@ exports[`surrounding-empty-line.html - html-verify 1`] = `
|
|||
><!-- 3
|
||||
--></span>
|
||||
|
||||
123<!--
|
||||
-->456 123<!--
|
||||
x
|
||||
-->456
|
||||
|
||||
<!--
|
||||
A
|
||||
B
|
||||
-->
|
||||
|
||||
<!--
|
||||
The null hero's name is {{nullHero.name}}
|
||||
|
||||
See console log:
|
||||
TypeError: Cannot read property 'name' of null in [null]
|
||||
-->
|
||||
|
||||
<!--
|
||||
The null hero's name is {{nullHero.name}}
|
||||
|
||||
See console log:
|
||||
TypeError: Cannot read property 'name' of null in [null]
|
||||
-->
|
||||
|
||||
`;
|
||||
|
||||
exports[`surrounding-empty-line.html - html-verify 2`] = `
|
||||
|
@ -487,25 +767,52 @@ exports[`surrounding-empty-line.html - html-verify 2`] = `
|
|||
1 --><span>a</span><!--
|
||||
2 --><span>b</span><!--
|
||||
3 --></span>
|
||||
|
||||
123<!---->456
|
||||
|
||||
123<!--x-->456
|
||||
|
||||
<!-- A
|
||||
B -->
|
||||
|
||||
<!--
|
||||
The null hero's name is {{nullHero.name}}
|
||||
|
||||
See console log:
|
||||
TypeError: Cannot read property 'name' of null in [null]
|
||||
-->
|
||||
|
||||
<!--
|
||||
The null hero's name is {{nullHero.name}}
|
||||
|
||||
See console log:
|
||||
TypeError: Cannot read property 'name' of null in [null]
|
||||
-->
|
||||
~
|
||||
<ul>
|
||||
<!--
|
||||
123
|
||||
--><li
|
||||
>First</li
|
||||
><!--
|
||||
-->
|
||||
<li>
|
||||
First
|
||||
</li>
|
||||
<!--
|
||||
123
|
||||
456
|
||||
789
|
||||
--><li
|
||||
>Second</li
|
||||
><!--
|
||||
-->
|
||||
<li>
|
||||
Second
|
||||
</li>
|
||||
<!--
|
||||
123
|
||||
456
|
||||
789
|
||||
--><li
|
||||
>Second</li
|
||||
><!--
|
||||
-->
|
||||
<li>
|
||||
Second
|
||||
</li>
|
||||
<!--
|
||||
123
|
||||
456
|
||||
789
|
||||
|
@ -547,6 +854,31 @@ exports[`surrounding-empty-line.html - html-verify 2`] = `
|
|||
3
|
||||
--></span>
|
||||
|
||||
123<!--
|
||||
-->456
|
||||
123<!--
|
||||
x
|
||||
-->456
|
||||
|
||||
<!--
|
||||
A
|
||||
B
|
||||
-->
|
||||
|
||||
<!--
|
||||
The null hero's name is {{nullHero.name}}
|
||||
|
||||
See console log:
|
||||
TypeError: Cannot read property 'name' of null in [null]
|
||||
-->
|
||||
|
||||
<!--
|
||||
The null hero's name is {{nullHero.name}}
|
||||
|
||||
See console log:
|
||||
TypeError: Cannot read property 'name' of null in [null]
|
||||
-->
|
||||
|
||||
`;
|
||||
|
||||
exports[`surrounding-empty-line.html - html-verify 3`] = `
|
||||
|
@ -585,21 +917,44 @@ exports[`surrounding-empty-line.html - html-verify 3`] = `
|
|||
1 --><span>a</span><!--
|
||||
2 --><span>b</span><!--
|
||||
3 --></span>
|
||||
|
||||
123<!---->456
|
||||
|
||||
123<!--x-->456
|
||||
|
||||
<!-- A
|
||||
B -->
|
||||
|
||||
<!--
|
||||
The null hero's name is {{nullHero.name}}
|
||||
|
||||
See console log:
|
||||
TypeError: Cannot read property 'name' of null in [null]
|
||||
-->
|
||||
|
||||
<!--
|
||||
The null hero's name is {{nullHero.name}}
|
||||
|
||||
See console log:
|
||||
TypeError: Cannot read property 'name' of null in [null]
|
||||
-->
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
<ul>
|
||||
<!-- 123
|
||||
--><li>First</li
|
||||
><!--
|
||||
<!-- 123 -->
|
||||
<li>First</li>
|
||||
<!--
|
||||
123
|
||||
456
|
||||
789
|
||||
--><li>Second</li
|
||||
><!--
|
||||
-->
|
||||
<li>Second</li>
|
||||
<!--
|
||||
123
|
||||
456
|
||||
789
|
||||
--><li>Second</li
|
||||
><!--
|
||||
-->
|
||||
<li>Second</li>
|
||||
<!--
|
||||
123
|
||||
456
|
||||
789
|
||||
|
@ -629,4 +984,284 @@ exports[`surrounding-empty-line.html - html-verify 3`] = `
|
|||
><!-- 3
|
||||
--></span>
|
||||
|
||||
123<!--
|
||||
-->456 123<!--
|
||||
x
|
||||
-->456
|
||||
|
||||
<!--
|
||||
A
|
||||
B
|
||||
-->
|
||||
|
||||
<!--
|
||||
The null hero's name is {{nullHero.name}}
|
||||
|
||||
See console log:
|
||||
TypeError: Cannot read property 'name' of null in [null]
|
||||
-->
|
||||
|
||||
<!--
|
||||
The null hero's name is {{nullHero.name}}
|
||||
|
||||
See console log:
|
||||
TypeError: Cannot read property 'name' of null in [null]
|
||||
-->
|
||||
|
||||
`;
|
||||
|
||||
exports[`surrounding-empty-line.html - html-verify 4`] = `
|
||||
<ul><!-- 123
|
||||
--><li>First</li><!-- 123
|
||||
456
|
||||
789
|
||||
--><li>Second</li><!--
|
||||
|
||||
|
||||
123
|
||||
456
|
||||
789
|
||||
|
||||
|
||||
--><li>Second</li><!--
|
||||
|
||||
|
||||
123
|
||||
456
|
||||
789
|
||||
|
||||
|
||||
--></ul>
|
||||
<span><!--
|
||||
--><span>a</span><!--
|
||||
--><span>b</span><!--
|
||||
--></span>
|
||||
|
||||
<span><!-- 1
|
||||
--><span>a</span><!-- 2
|
||||
--><span>b</span><!-- 3
|
||||
--></span>
|
||||
|
||||
<span><!--
|
||||
1 --><span>a</span><!--
|
||||
2 --><span>b</span><!--
|
||||
3 --></span>
|
||||
|
||||
123<!---->456
|
||||
|
||||
123<!--x-->456
|
||||
|
||||
<!-- A
|
||||
B -->
|
||||
|
||||
<!--
|
||||
The null hero's name is {{nullHero.name}}
|
||||
|
||||
See console log:
|
||||
TypeError: Cannot read property 'name' of null in [null]
|
||||
-->
|
||||
|
||||
<!--
|
||||
The null hero's name is {{nullHero.name}}
|
||||
|
||||
See console log:
|
||||
TypeError: Cannot read property 'name' of null in [null]
|
||||
-->
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
<ul
|
||||
><!-- 123
|
||||
--><li>First</li
|
||||
><!--
|
||||
123
|
||||
456
|
||||
789
|
||||
--><li>Second</li
|
||||
><!--
|
||||
123
|
||||
456
|
||||
789
|
||||
--><li>Second</li
|
||||
><!--
|
||||
123
|
||||
456
|
||||
789
|
||||
--></ul>
|
||||
<span
|
||||
><!--
|
||||
--><span>a</span
|
||||
><!--
|
||||
--><span>b</span
|
||||
><!--
|
||||
--></span>
|
||||
|
||||
<span
|
||||
><!-- 1
|
||||
--><span>a</span
|
||||
><!-- 2
|
||||
--><span>b</span
|
||||
><!-- 3
|
||||
--></span>
|
||||
|
||||
<span
|
||||
><!-- 1
|
||||
--><span>a</span
|
||||
><!-- 2
|
||||
--><span>b</span
|
||||
><!-- 3
|
||||
--></span>
|
||||
|
||||
123<!--
|
||||
-->456 123<!--
|
||||
x
|
||||
-->456
|
||||
|
||||
<!--
|
||||
A
|
||||
B
|
||||
-->
|
||||
|
||||
<!--
|
||||
The null hero's name is {{nullHero.name}}
|
||||
|
||||
See console log:
|
||||
TypeError: Cannot read property 'name' of null in [null]
|
||||
-->
|
||||
|
||||
<!--
|
||||
The null hero's name is {{nullHero.name}}
|
||||
|
||||
See console log:
|
||||
TypeError: Cannot read property 'name' of null in [null]
|
||||
-->
|
||||
|
||||
`;
|
||||
|
||||
exports[`surrounding-empty-line.html - html-verify 5`] = `
|
||||
<ul><!-- 123
|
||||
--><li>First</li><!-- 123
|
||||
456
|
||||
789
|
||||
--><li>Second</li><!--
|
||||
|
||||
|
||||
123
|
||||
456
|
||||
789
|
||||
|
||||
|
||||
--><li>Second</li><!--
|
||||
|
||||
|
||||
123
|
||||
456
|
||||
789
|
||||
|
||||
|
||||
--></ul>
|
||||
<span><!--
|
||||
--><span>a</span><!--
|
||||
--><span>b</span><!--
|
||||
--></span>
|
||||
|
||||
<span><!-- 1
|
||||
--><span>a</span><!-- 2
|
||||
--><span>b</span><!-- 3
|
||||
--></span>
|
||||
|
||||
<span><!--
|
||||
1 --><span>a</span><!--
|
||||
2 --><span>b</span><!--
|
||||
3 --></span>
|
||||
|
||||
123<!---->456
|
||||
|
||||
123<!--x-->456
|
||||
|
||||
<!-- A
|
||||
B -->
|
||||
|
||||
<!--
|
||||
The null hero's name is {{nullHero.name}}
|
||||
|
||||
See console log:
|
||||
TypeError: Cannot read property 'name' of null in [null]
|
||||
-->
|
||||
|
||||
<!--
|
||||
The null hero's name is {{nullHero.name}}
|
||||
|
||||
See console log:
|
||||
TypeError: Cannot read property 'name' of null in [null]
|
||||
-->
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
<ul>
|
||||
<!-- 123 -->
|
||||
<li>First</li>
|
||||
<!--
|
||||
123
|
||||
456
|
||||
789
|
||||
-->
|
||||
<li>Second</li>
|
||||
<!--
|
||||
123
|
||||
456
|
||||
789
|
||||
-->
|
||||
<li>Second</li>
|
||||
<!--
|
||||
123
|
||||
456
|
||||
789
|
||||
-->
|
||||
</ul>
|
||||
<span>
|
||||
<!---->
|
||||
<span>a</span>
|
||||
<!---->
|
||||
<span>b</span>
|
||||
<!---->
|
||||
</span>
|
||||
|
||||
<span>
|
||||
<!-- 1 -->
|
||||
<span>a</span>
|
||||
<!-- 2 -->
|
||||
<span>b</span>
|
||||
<!-- 3 -->
|
||||
</span>
|
||||
|
||||
<span>
|
||||
<!-- 1 -->
|
||||
<span>a</span>
|
||||
<!-- 2 -->
|
||||
<span>b</span>
|
||||
<!-- 3 -->
|
||||
</span>
|
||||
|
||||
123
|
||||
<!---->
|
||||
456 123
|
||||
<!-- x -->
|
||||
456
|
||||
|
||||
<!--
|
||||
A
|
||||
B
|
||||
-->
|
||||
|
||||
<!--
|
||||
The null hero's name is {{nullHero.name}}
|
||||
|
||||
See console log:
|
||||
TypeError: Cannot read property 'name' of null in [null]
|
||||
-->
|
||||
|
||||
<!--
|
||||
The null hero's name is {{nullHero.name}}
|
||||
|
||||
See console log:
|
||||
TypeError: Cannot read property 'name' of null in [null]
|
||||
-->
|
||||
|
||||
`;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
run_spec(__dirname, ["html"]);
|
||||
run_spec(__dirname, ["html"], { printWidth: 1 });
|
||||
run_spec(__dirname, ["html"], { printWidth: 999 });
|
||||
run_spec(__dirname, ["html"], { htmlWhitespaceSensitivity: "strict" });
|
||||
run_spec(__dirname, ["html"], { htmlWhitespaceSensitivity: "ignore" });
|
||||
|
|
|
@ -33,3 +33,24 @@
|
|||
1 --><span>a</span><!--
|
||||
2 --><span>b</span><!--
|
||||
3 --></span>
|
||||
|
||||
123<!---->456
|
||||
|
||||
123<!--x-->456
|
||||
|
||||
<!-- A
|
||||
B -->
|
||||
|
||||
<!--
|
||||
The null hero's name is {{nullHero.name}}
|
||||
|
||||
See console log:
|
||||
TypeError: Cannot read property 'name' of null in [null]
|
||||
-->
|
||||
|
||||
<!--
|
||||
The null hero's name is {{nullHero.name}}
|
||||
|
||||
See console log:
|
||||
TypeError: Cannot read property 'name' of null in [null]
|
||||
-->
|
||||
|
|
|
@ -36,7 +36,7 @@ exports[`less.html - html-verify 1`] = `
|
|||
</style>
|
||||
|
||||
<style lang="less">
|
||||
@nice-blue: #5B83AD;
|
||||
@nice-blue: #5b83ad;
|
||||
@light-blue: @nice-blue + #111;
|
||||
|
||||
#header {
|
||||
|
@ -91,6 +91,19 @@ exports[`scss.html - html-verify 1`] = `
|
|||
color: $primary-color;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.someElement {
|
||||
@include bp-medium {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@include bp-large {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
<style type="text/x-scss">
|
||||
$font-stack: Helvetica, sans-serif;
|
||||
|
@ -112,6 +125,19 @@ exports[`scss.html - html-verify 1`] = `
|
|||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.someElement {
|
||||
@include bp-medium {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@include bp-large {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
`;
|
||||
|
||||
exports[`simple.html - html-verify 1`] = `
|
||||
|
|
|
@ -17,3 +17,16 @@
|
|||
color: $primary-color;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.someElement {
|
||||
@include bp-medium {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@include bp-large {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`example.html - html-verify 1`] = `
|
||||
<!--interpolations in html should be treated as normal text--><div>Fuga magnam facilis. Voluptatem quaerat porro.{{
|
||||
|
||||
|
||||
x => {
|
||||
const hello = 'world'
|
||||
return hello;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}} Magni consectetur in et molestias neque esse voluptatibus voluptas. {{
|
||||
|
||||
|
||||
some_variable
|
||||
|
||||
|
||||
|
||||
}} Eum quia nihil nulla esse. Dolorem asperiores vero est error {{
|
||||
|
||||
preserve
|
||||
|
||||
invalid
|
||||
|
||||
interpolation
|
||||
|
||||
}} reprehenderit voluptates minus {{console.log( short_interpolation )}} nemo.</div>
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
<!-- interpolations in html should be treated as normal text -->
|
||||
<div>
|
||||
Fuga magnam facilis. Voluptatem quaerat porro.{{ x => { const hello = 'world'
|
||||
return hello; } }} Magni consectetur in et molestias neque esse voluptatibus
|
||||
voluptas. {{ some_variable }} Eum quia nihil nulla esse. Dolorem asperiores
|
||||
vero est error {{ preserve invalid interpolation }} reprehenderit voluptates
|
||||
minus {{console.log( short_interpolation )}} nemo.
|
||||
</div>
|
||||
|
||||
`;
|
|
@ -0,0 +1,26 @@
|
|||
<!--interpolations in html should be treated as normal text--><div>Fuga magnam facilis. Voluptatem quaerat porro.{{
|
||||
|
||||
|
||||
x => {
|
||||
const hello = 'world'
|
||||
return hello;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}} Magni consectetur in et molestias neque esse voluptatibus voluptas. {{
|
||||
|
||||
|
||||
some_variable
|
||||
|
||||
|
||||
|
||||
}} Eum quia nihil nulla esse. Dolorem asperiores vero est error {{
|
||||
|
||||
preserve
|
||||
|
||||
invalid
|
||||
|
||||
interpolation
|
||||
|
||||
}} reprehenderit voluptates minus {{console.log( short_interpolation )}} nemo.</div>
|
|
@ -0,0 +1 @@
|
|||
run_spec(__dirname, ["html"]);
|
|
@ -26,6 +26,11 @@ exports[`js.html - html-verify 1`] = `
|
|||
<script type="text/babel">
|
||||
const someJS = 'this should be formatted'
|
||||
</script>
|
||||
<script type="module">
|
||||
import lib from './lib.js';
|
||||
|
||||
function myFunction() { return 'foo'; }
|
||||
</script>
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
<script type="text/javascript">
|
||||
var message = "Alert!";
|
||||
|
@ -45,6 +50,13 @@ exports[`js.html - html-verify 1`] = `
|
|||
<script type="text/babel">
|
||||
const someJS = "this should be formatted";
|
||||
</script>
|
||||
<script type="module">
|
||||
import lib from "./lib.js";
|
||||
|
||||
function myFunction() {
|
||||
return "foo";
|
||||
}
|
||||
</script>
|
||||
|
||||
`;
|
||||
|
||||
|
@ -246,31 +258,34 @@ exports[`typescript.html - html-verify 1`] = `
|
|||
document.body.innerHTML = greeter(user);
|
||||
</script>
|
||||
<script lang="tsx">
|
||||
class CommentBox extends React.Component<{ url: string, pollInterval: number}, CommentData> {
|
||||
class CommentBox extends React.Component<
|
||||
{ url: string; pollInterval: number },
|
||||
CommentData
|
||||
> {
|
||||
constructor() {
|
||||
super()
|
||||
super();
|
||||
this.state = { data: [] };
|
||||
}
|
||||
fetchComments() {
|
||||
$.ajax({
|
||||
url: this.props.url,
|
||||
dataType: 'json',
|
||||
dataType: "json",
|
||||
cache: false,
|
||||
success: (data) => this.setState({ data: data }),
|
||||
success: data => this.setState({ data: data }),
|
||||
error: (xhr, status, err) => console.error(status, err)
|
||||
})
|
||||
});
|
||||
}
|
||||
componentDidMount() {
|
||||
this.fetchComments();
|
||||
setInterval(this.fetchComments.bind(this), this.props.pollInterval);
|
||||
}
|
||||
render() {
|
||||
let handleCommentSubmit = (comment: { author: string, text: string }) => {
|
||||
console.warn('comment submitted!', comment);
|
||||
let handleCommentSubmit = (comment: { author: string; text: string }) => {
|
||||
console.warn("comment submitted!", comment);
|
||||
const updated = this.state.data.slice(0);
|
||||
updated.push(comment);
|
||||
this.setState({ data: updated });
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="commentBox">
|
||||
<h1>Comments</h1>
|
||||
|
|
|
@ -16,3 +16,8 @@
|
|||
<script type="text/babel">
|
||||
const someJS = 'this should be formatted'
|
||||
</script>
|
||||
<script type="module">
|
||||
import lib from './lib.js';
|
||||
|
||||
function myFunction() { return 'foo'; }
|
||||
</script>
|
||||
|
|
|
@ -13,6 +13,14 @@ exports[`standalone-end-marker.html - html-verify 1`] = `
|
|||
>
|
||||
|
||||
<span></span>
|
||||
|
||||
<div>
|
||||
<a href="#123123123123123131231312321312312312312312312312312313123123123123123"
|
||||
>123123123123</a
|
||||
>
|
||||
|
||||
123123
|
||||
</div>
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
<div></div>
|
||||
<span></span>
|
||||
|
@ -22,4 +30,13 @@ exports[`standalone-end-marker.html - html-verify 1`] = `
|
|||
|
||||
<span></span>
|
||||
|
||||
<div>
|
||||
<a
|
||||
href="#123123123123123131231312321312312312312312312312312313123123123123123"
|
||||
>123123123123</a
|
||||
>
|
||||
|
||||
123123
|
||||
</div>
|
||||
|
||||
`;
|
||||
|
|
|
@ -10,3 +10,11 @@
|
|||
>
|
||||
|
||||
<span></span>
|
||||
|
||||
<div>
|
||||
<a href="#123123123123123131231312321312312312312312312312312313123123123123123"
|
||||
>123123123123</a
|
||||
>
|
||||
|
||||
123123
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,20 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`cases.html - html-verify 1`] = `
|
||||
123<!--prettier-ignore-->456
|
||||
|
||||
<span></span><!--prettier-ignore--><span></span>
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
123<!--
|
||||
prettier-ignore
|
||||
-->456
|
||||
|
||||
<span></span
|
||||
><!-- prettier-ignore
|
||||
--><span></span>
|
||||
|
||||
`;
|
||||
|
||||
exports[`document.html - html-verify 1`] = `
|
||||
<!doctype html>
|
||||
<html class="no-js" lang="en">
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
123<!--prettier-ignore-->456
|
||||
|
||||
<span></span><!--prettier-ignore--><span></span>
|
|
@ -12,6 +12,39 @@ exports[`svg.html - html-verify 1`] = `
|
|||
</svg>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs /> <style >
|
||||
polygon { fill: black }
|
||||
|
||||
div {
|
||||
color: white;
|
||||
font:18px serif;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<g>
|
||||
<g><polygon points="5,5 195,10 185,185 10,195" />
|
||||
<text> Text</text></g>
|
||||
</g>
|
||||
|
||||
<!-- Common use case: embed HTML text into SVG -->
|
||||
<foreignObject x="20" y="20" width="160" height="160">
|
||||
<!--
|
||||
In the context of SVG embeded into HTML, the XHTML namespace could be avoided, but it is mandatory in the context of an SVG document
|
||||
-->
|
||||
<div xmlns="http://www.w3.org/1999/xhtml">
|
||||
<p>
|
||||
123
|
||||
</p>
|
||||
<span>
|
||||
123
|
||||
</span>
|
||||
</div>
|
||||
</foreignObject>
|
||||
</svg>
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
@ -32,4 +65,38 @@ exports[`svg.html - html-verify 1`] = `
|
|||
</body>
|
||||
</html>
|
||||
|
||||
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs />
|
||||
<style>
|
||||
polygon {
|
||||
fill: black;
|
||||
}
|
||||
|
||||
div {
|
||||
color: white;
|
||||
font: 18px serif;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<g>
|
||||
<g>
|
||||
<polygon points="5,5 195,10 185,185 10,195" />
|
||||
<text>Text</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Common use case: embed HTML text into SVG -->
|
||||
<foreignObject x="20" y="20" width="160" height="160">
|
||||
<!--
|
||||
In the context of SVG embeded into HTML, the XHTML namespace could be avoided, but it is mandatory in the context of an SVG document
|
||||
-->
|
||||
<div xmlns="http://www.w3.org/1999/xhtml">
|
||||
<p>123</p>
|
||||
<span> 123 </span>
|
||||
</div>
|
||||
</foreignObject>
|
||||
</svg>
|
||||
|
||||
`;
|
||||
|
|
|
@ -9,3 +9,36 @@
|
|||
</svg>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs /> <style >
|
||||
polygon { fill: black }
|
||||
|
||||
div {
|
||||
color: white;
|
||||
font:18px serif;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<g>
|
||||
<g><polygon points="5,5 195,10 185,185 10,195" />
|
||||
<text> Text</text></g>
|
||||
</g>
|
||||
|
||||
<!-- Common use case: embed HTML text into SVG -->
|
||||
<foreignObject x="20" y="20" width="160" height="160">
|
||||
<!--
|
||||
In the context of SVG embeded into HTML, the XHTML namespace could be avoided, but it is mandatory in the context of an SVG document
|
||||
-->
|
||||
<div xmlns="http://www.w3.org/1999/xhtml">
|
||||
<p>
|
||||
123
|
||||
</p>
|
||||
<span>
|
||||
123
|
||||
</span>
|
||||
</div>
|
||||
</foreignObject>
|
||||
</svg>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,2 @@
|
|||
<app-foo></app-foo>
|
||||
<app-bar></app-bar>
|
|
@ -1,4 +0,0 @@
|
|||
<div>
|
||||
<ABC />
|
||||
<span></span>
|
||||
</div>
|
|
@ -59,3 +59,52 @@
|
|||
>
|
||||
<span>*<b>200</b></span>
|
||||
<img src="longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong" />123
|
||||
<div>123<meta attr/>456</div>
|
||||
<p>x<span a="b"></span></p>
|
||||
<p>x<meta a></p>
|
||||
<p>x<meta></p>
|
||||
<span></span>
|
||||
|
||||
<label aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa></label> |
|
||||
<span></span>
|
||||
<br />
|
||||
<button xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
>12345678901234567890</button
|
||||
> <br /><br />
|
||||
|
||||
<button bind-disabled="isUnchanged" on-click="onSave($event)"
|
||||
>Disabled Cancel</button
|
||||
>
|
||||
<br /><br />
|
||||
<button xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
>12345678901234567890</button
|
||||
> <br /><br />
|
||||
|
||||
<button bind-disabled="isUnchanged" on-click="onSave($event)"
|
||||
>Disabled Cancel</button
|
||||
>
|
||||
<br /><br />
|
||||
<p>"<span [innerHTML]="title"></span>" is the <i>property bound</i> title.</p>
|
||||
<li>12345678901234567890123456789012345678901234567890123456789012345678901234567890</li>
|
||||
<div>
|
||||
<app-nav></app-nav>
|
||||
<router-outlet></router-outlet>
|
||||
<app-footer></app-footer>
|
||||
|
||||
<app-nav [input]="something"></app-nav>
|
||||
<router-outlet></router-outlet>
|
||||
<app-footer></app-footer>
|
||||
|
||||
<app-primary-navigation></app-primary-navigation>
|
||||
<router-outlet></router-outlet>
|
||||
<app-footer [input]="something"></app-footer>
|
||||
</div>
|
||||
<x:root><SPAN>foreign tag name should not be lower cased</SPAN></x:root>
|
||||
<div>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
|
||||
"<strong>seddoeiusmod</strong>".
|
||||
</div>
|
||||
<div>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
|
||||
<strong>seddoeiusmod</strong>.
|
||||
</div>
|
||||
|
|
|
@ -26,3 +26,5 @@
|
|||
</div>
|
||||
</div>
|
||||
<textarea></textarea>
|
||||
|
||||
<div><textarea>lorem ipsum</textarea></div>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,25 @@
|
|||
<div
|
||||
v-for=" item in items "
|
||||
v-for=" item of items "
|
||||
v-for="( item , index) in items"
|
||||
v-for="value in object"
|
||||
v-for="(value, key) in object"
|
||||
v-for="(value, key) of object"
|
||||
v-for="(value , key, index) in object"
|
||||
v-for=" n in evenNumbers"
|
||||
v-for=" n in even ( numbers) "
|
||||
v-for=" n in 10"
|
||||
v-for=" { a } in [0].map(()=>({a:1})) "
|
||||
v-for=" ({ a }, [c ]) in [0].map(()=>1) "
|
||||
v-for=" n in items.map(x => { return x }) "
|
||||
@click=" /* hello */ "
|
||||
@click=" /* 1 */ $emit( /* 2 */ 'click' /* 3 */ ) /* 4 */ ; /* 5 */ "
|
||||
@click=" $emit( 'click' ) "
|
||||
@click=" $emit( 'click' ) ;"
|
||||
@click=" $emit( 'click' ) ;if(something){for(let i=j;i<100;i++){}}else{}"
|
||||
slot-scope="{destructuring:{a:{b}}}"
|
||||
:class="{ longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong: true }"
|
||||
:class="(() => { return 'hello' })()"
|
||||
:key="index /* hello */ "
|
||||
:key="index // hello "
|
||||
></div>
|
|
@ -0,0 +1,7 @@
|
|||
<!-- vue filters are only allowed in v-bind and interpolation -->
|
||||
<template>
|
||||
<div class="allowed">{{value | thisIsARealSuperLongFilterPipe("arg1", arg2) | anotherPipeLongJustForFun | pipeTheThird}}</div>
|
||||
<div class="allowed" v-bind:something='value | thisIsARealSuperLongFilterPipe("arg1", arg2) | anotherPipeLongJustForFun | pipeTheThird'></div>
|
||||
<div class="allowed" :class='value | thisIsARealSuperLongFilterPipe("arg1", arg2) | anotherPipeLongJustForFun | pipeTheThird'></div>
|
||||
<div class="not-allowed" v-if='value | thisIsARealSuperLongFilterPipe("arg1", arg2) | anotherPipeLongJustForFun | pipeTheThird'></div>
|
||||
</template>
|
|
@ -0,0 +1,56 @@
|
|||
<div>Fuga magnam facilis. Voluptatem quaerat porro.{{
|
||||
|
||||
|
||||
x => {
|
||||
const hello = 'world'
|
||||
return hello;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}} Magni consectetur in et molestias neque esse voluptatibus voluptas. {{
|
||||
|
||||
|
||||
some_variable
|
||||
|
||||
|
||||
|
||||
}} Eum quia nihil nulla esse. Dolorem asperiores vero est error {{
|
||||
|
||||
preserve
|
||||
|
||||
invalid
|
||||
|
||||
interpolation
|
||||
|
||||
}} reprehenderit voluptates minus {{console.log( short_interpolation )}} nemo.</div>
|
||||
|
||||
<script type="text/jsx">
|
||||
export default {
|
||||
render (h) {
|
||||
return (
|
||||
<ul
|
||||
class={{
|
||||
'a': b,
|
||||
'c': d,
|
||||
"e": f
|
||||
}}
|
||||
>
|
||||
{ this.xyz }
|
||||
</ul>
|
||||
)
|
||||
};
|
||||
</script>
|
||||
|
||||
<div>
|
||||
1234567890123456789012345678901234567890123456789012345678901234567890{{ something }}1234567890
|
||||
</div>
|
||||
<div>
|
||||
1234567890123456789012345678901234567890123456789012345678901234567890 {{ something }}1234567890
|
||||
</div>
|
||||
<div>
|
||||
1234567890123456789012345678901234567890123456789012345678901234567890{{ something }} 1234567890
|
||||
</div>
|
||||
<div>
|
||||
1234567890123456789012345678901234567890123456789012345678901234567890 {{ something }} 1234567890
|
||||
</div>
|
|
@ -0,0 +1,28 @@
|
|||
<!-- copied from https://github.com/gitlabhq/gitlabhq/blob/master/app/assets/javascripts/ide/components/jobs/detail.vue -->
|
||||
<pre
|
||||
ref="buildTrace"
|
||||
class="build-trace mb-0 h-100"
|
||||
@scroll="scrollBuildLog"
|
||||
>
|
||||
<code
|
||||
v-show="!detailJob.isLoading"
|
||||
class="bash"
|
||||
v-html="jobOutput"
|
||||
>
|
||||
</code>
|
||||
<div
|
||||
v-show="detailJob.isLoading"
|
||||
class="build-loader-animation"
|
||||
>
|
||||
<div class="dot"></div>
|
||||
<div class="dot"></div>
|
||||
<div class="dot"></div>
|
||||
</div>
|
||||
</pre>
|
||||
|
||||
<!-- copied from https://github.com/gitlabhq/gitlabhq/blob/master/app/assets/javascripts/vue_shared/components/code_block.vue -->
|
||||
<template>
|
||||
<pre class="code-block rounded">
|
||||
<code class="d-block">{{ code }}</code>
|
||||
</pre>
|
||||
</template>
|
|
@ -0,0 +1,27 @@
|
|||
<!--copied from https://github.com/gitlabhq/gitlabhq/blob/master/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue-->
|
||||
<template>
|
||||
<div class="file-container">
|
||||
<div class="file-content image_file">
|
||||
<img
|
||||
ref="contentImg"
|
||||
:class="{ 'is-zoomable': isZoomable, 'is-zoomed': isZoomed }"
|
||||
:src="path"
|
||||
:alt="path"
|
||||
@load="onImgLoad"
|
||||
@click="onImgClick"/>
|
||||
<p
|
||||
v-if="renderInfo"
|
||||
class="file-info prepend-top-10">
|
||||
<template v-if="fileSize>0">
|
||||
{{ fileSizeReadable }}
|
||||
</template>
|
||||
<template v-if="fileSize>0 && width && height">
|
||||
|
|
||||
</template>
|
||||
<template v-if="width && height">
|
||||
W: {{ width }} | H: {{ height }}
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -13,6 +13,48 @@ exports[`break-tags.html - html-verify 1`] = `
|
|||
|
||||
`;
|
||||
|
||||
exports[`display-inline-block.html - html-verify 1`] = `
|
||||
<button>Click here! Click here! Click here! Click here! Click here! Click here!</button>
|
||||
<button>
|
||||
Click here! Click here! Click here! Click here! Click here! Click here!
|
||||
</button>
|
||||
<div>
|
||||
<button>Click here! Click here! Click here! Click here! Click here! Click here!</button><button>Click here! Click here! Click here! Click here! Click here! Click here!</button>
|
||||
</div>
|
||||
<div>
|
||||
<button>Click here! Click here! Click here! Click here! Click here! Click here!</button>
|
||||
<button>Click here! Click here! Click here! Click here! Click here! Click here!</button>
|
||||
</div>
|
||||
<video src="brave.webm"><track kind=subtitles src=brave.en.vtt srclang=en label="English"><track kind=subtitles src=brave.en.vtt srclang=en label="English"></video>
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
<button>
|
||||
Click here! Click here! Click here! Click here! Click here! Click here!
|
||||
</button>
|
||||
<button>
|
||||
Click here! Click here! Click here! Click here! Click here! Click here!
|
||||
</button>
|
||||
<div>
|
||||
<button>
|
||||
Click here! Click here! Click here! Click here! Click here! Click here!</button
|
||||
><button>
|
||||
Click here! Click here! Click here! Click here! Click here! Click here!
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button>
|
||||
Click here! Click here! Click here! Click here! Click here! Click here!
|
||||
</button>
|
||||
<button>
|
||||
Click here! Click here! Click here! Click here! Click here! Click here!
|
||||
</button>
|
||||
</div>
|
||||
<video src="brave.webm">
|
||||
<track kind="subtitles" src="brave.en.vtt" srclang="en" label="English" />
|
||||
<track kind="subtitles" src="brave.en.vtt" srclang="en" label="English" />
|
||||
</video>
|
||||
|
||||
`;
|
||||
|
||||
exports[`display-none.html - html-verify 1`] = `
|
||||
<!DOCTYPE html><HTML CLASS="no-js mY-ClAsS"><HEAD><META CHARSET="utf-8"><TITLE>My tITlE</TITLE><META NAME="description" content="My CoNtEnT"></HEAD></HTML>
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -109,6 +151,24 @@ conubia nostra, per inceptos himenaeos. Donec in ornare velit.</p>
|
|||
|
||||
`;
|
||||
|
||||
exports[`non-breaking-whitespace.html - html-verify 1`] = `
|
||||
<!-- normal whitespaces -->
|
||||
<span>Nihil aut odit omnis. Quam maxime est molestiae. Maxime dolorem dolores voluptas quaerat ut qui sunt vitae error.</span>
|
||||
<!-- non-breaking whitespaces -->
|
||||
<span>Nihil aut odit omnis. Quam maxime est molestiae. Maxime dolorem dolores voluptas quaerat ut qui sunt vitae error.</span>
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
<!-- normal whitespaces -->
|
||||
<span
|
||||
>Nihil aut odit omnis. Quam maxime est molestiae. Maxime dolorem dolores
|
||||
voluptas quaerat ut qui sunt vitae error.</span
|
||||
>
|
||||
<!-- non-breaking whitespaces -->
|
||||
<span
|
||||
>Nihil aut odit omnis. Quam maxime est molestiae. Maxime dolorem dolores voluptas quaerat ut qui sunt vitae error.</span
|
||||
>
|
||||
|
||||
`;
|
||||
|
||||
exports[`table.html - html-verify 1`] = `
|
||||
<table>
|
||||
<thead>
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<button>Click here! Click here! Click here! Click here! Click here! Click here!</button>
|
||||
<button>
|
||||
Click here! Click here! Click here! Click here! Click here! Click here!
|
||||
</button>
|
||||
<div>
|
||||
<button>Click here! Click here! Click here! Click here! Click here! Click here!</button><button>Click here! Click here! Click here! Click here! Click here! Click here!</button>
|
||||
</div>
|
||||
<div>
|
||||
<button>Click here! Click here! Click here! Click here! Click here! Click here!</button>
|
||||
<button>Click here! Click here! Click here! Click here! Click here! Click here!</button>
|
||||
</div>
|
||||
<video src="brave.webm"><track kind=subtitles src=brave.en.vtt srclang=en label="English"><track kind=subtitles src=brave.en.vtt srclang=en label="English"></video>
|
|
@ -0,0 +1,4 @@
|
|||
<!-- normal whitespaces -->
|
||||
<span>Nihil aut odit omnis. Quam maxime est molestiae. Maxime dolorem dolores voluptas quaerat ut qui sunt vitae error.</span>
|
||||
<!-- non-breaking whitespaces -->
|
||||
<span>Nihil aut odit omnis. Quam maxime est molestiae. Maxime dolorem dolores voluptas quaerat ut qui sunt vitae error.</span>
|
|
@ -0,0 +1,21 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`with-pragma.html - html-verify 1`] = `
|
||||
<!-- @prettier -->
|
||||
|
||||
Hello World
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
<!-- @prettier -->
|
||||
|
||||
Hello World
|
||||
|
||||
`;
|
||||
|
||||
exports[`without-pragma.html - html-verify 1`] = `
|
||||
Hello World
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
<!-- @format -->
|
||||
|
||||
Hello World
|
||||
|
||||
`;
|
|
@ -0,0 +1 @@
|
|||
run_spec(__dirname, ["html"], { insertPragma: true });
|
|
@ -0,0 +1,3 @@
|
|||
<!-- @prettier -->
|
||||
|
||||
Hello World
|
|
@ -0,0 +1 @@
|
|||
Hello World
|
|
@ -20,8 +20,7 @@ const x = 1;
|
|||
<script>
|
||||
const x = 1;
|
||||
</script>
|
||||
<style src="./my-component.css">
|
||||
</style>
|
||||
<style src="./my-component.css"></style>
|
||||
|
||||
`;
|
||||
|
||||
|
@ -46,7 +45,6 @@ const x = 1;
|
|||
<script>
|
||||
const x = 1;
|
||||
</script>
|
||||
<style src="./my-component.css">
|
||||
</style>
|
||||
<style src="./my-component.css"></style>
|
||||
|
||||
`;
|
||||
|
|
|
@ -28,6 +28,62 @@ exports[`block-comment.json - json5-verify 2`] = `
|
|||
|
||||
`;
|
||||
|
||||
exports[`bottom-block-comment.json - json-verify 1`] = `
|
||||
1 /* block-comment */
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
1 /* block-comment */
|
||||
|
||||
`;
|
||||
|
||||
exports[`bottom-block-comment.json - json-verify 2`] = `
|
||||
1 /* block-comment */
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
1 /* block-comment */
|
||||
|
||||
`;
|
||||
|
||||
exports[`bottom-block-comment.json - json5-verify 1`] = `
|
||||
1 /* block-comment */
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
1 /* block-comment */
|
||||
|
||||
`;
|
||||
|
||||
exports[`bottom-block-comment.json - json5-verify 2`] = `
|
||||
1 /* block-comment */
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
1 /* block-comment */
|
||||
|
||||
`;
|
||||
|
||||
exports[`bottom-line-comment.json - json-verify 1`] = `
|
||||
1 // line-comment
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
1 // line-comment
|
||||
|
||||
`;
|
||||
|
||||
exports[`bottom-line-comment.json - json-verify 2`] = `
|
||||
1 // line-comment
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
1 // line-comment
|
||||
|
||||
`;
|
||||
|
||||
exports[`bottom-line-comment.json - json5-verify 1`] = `
|
||||
1 // line-comment
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
1 // line-comment
|
||||
|
||||
`;
|
||||
|
||||
exports[`bottom-line-comment.json - json5-verify 2`] = `
|
||||
1 // line-comment
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
1 // line-comment
|
||||
|
||||
`;
|
||||
|
||||
exports[`line-comment.json - json-verify 1`] = `
|
||||
{
|
||||
//comment
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
1 /* block-comment */
|
|
@ -0,0 +1 @@
|
|||
1 // line-comment
|
|
@ -0,0 +1,30 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`html-with-markdown-script.html - html-verify 1`] = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script type="text/markdown">
|
||||
# hello
|
||||
+ **foo**
|
||||
+ __bar__
|
||||
</script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script type="text/markdown">
|
||||
# hello
|
||||
|
||||
- **foo**
|
||||
- **bar**
|
||||
</script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
|
||||
`;
|
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script type="text/markdown">
|
||||
# hello
|
||||
+ **foo**
|
||||
+ __bar__
|
||||
</script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1 @@
|
|||
run_spec(__dirname, ["html"]);
|
|
@ -0,0 +1,85 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`lit-html.js - babylon-verify 1`] = `
|
||||
import { LitElement, html } from '@polymer/lit-element';
|
||||
|
||||
class MyElement extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
mood: { type: String }
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.mood = 'happy';
|
||||
}
|
||||
|
||||
render() {
|
||||
return html\`
|
||||
<style
|
||||
|
||||
|
||||
>
|
||||
.mood { color: green; }
|
||||
</style
|
||||
|
||||
|
||||
|
||||
>
|
||||
|
||||
Web Components are <span
|
||||
|
||||
|
||||
class="mood" >\${
|
||||
this.mood
|
||||
|
||||
}</span
|
||||
|
||||
>!
|
||||
\`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('my-element', MyElement);
|
||||
|
||||
const someHtml1 = html\`<div > hello \${world} </div >\`;
|
||||
const someHtml2 = /* HTML */ \`<div > hello \${world} </div >\`;
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
import { LitElement, html } from "@polymer/lit-element";
|
||||
|
||||
class MyElement extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
mood: { type: String }
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.mood = "happy";
|
||||
}
|
||||
|
||||
render() {
|
||||
return html\`
|
||||
<style>
|
||||
.mood {
|
||||
color: green;
|
||||
}
|
||||
</style>
|
||||
|
||||
Web Components are <span class="mood">\${this.mood}</span>!
|
||||
\`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("my-element", MyElement);
|
||||
|
||||
const someHtml1 = html\`
|
||||
<div>hello \${world}</div>
|
||||
\`;
|
||||
const someHtml2 = /* HTML */ \`
|
||||
<div>hello \${world}</div>
|
||||
\`;
|
||||
|
||||
`;
|
|
@ -0,0 +1 @@
|
|||
run_spec(__dirname, ["babylon"]);
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue