feat: support HTML/Vue/Angular (#5259)

master
Ika 2018-11-04 23:03:07 +08:00 committed by GitHub
parent 0878a6a3e7
commit 5e8a4a115a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
120 changed files with 15167 additions and 2423 deletions

View File

@ -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",

View File

@ -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",

View File

@ -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: {
},
// Vue
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;
}
}
},

View File

@ -464,9 +464,11 @@ function printString(raw, options, isDirectiveLiteral) {
const enclosingQuote =
options.parser === "json"
? double.quote
: shouldUseAlternateQuote
? alternate.quote
: preferred.quote;
: options.__isInHtmlAttribute
? single.quote
: shouldUseAlternateQuote
? alternate.quote
: preferred.quote;
// Directives are exact code unit sequences, which means that you can't
// change the escape sequences they use.
@ -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"
)
);
}

136
src/language-html/ast.js Normal file
View File

@ -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
};

View File

@ -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;
}
};

View File

@ -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";

View File

@ -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 = {

View File

@ -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(
{
type: "root",
children: handler.dom,
startIndex: 0,
endIndex: text.length
},
text
);
const rawAst = {
type: "root",
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, " "),
startSourceSpan: new ParseSourceSpan(
node.sourceSpan.start,
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
};
});
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;
endSourceSpan: new ParseSourceSpan(
contentStartSpan.moveBy(data.length),
node.sourceSpan.end
)
});
}
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 })
}
};

View File

@ -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;

View File

@ -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, "&quot;") : 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, "&quot;"), 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(/&quot;/g, '"').replace(/&apos;/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
};

View File

@ -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, "&quot;"), 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
};

View File

@ -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
};

View File

@ -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
};

View File

@ -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,34 +182,10 @@ 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 fn(newNode, stack);
}
function getPrevNode(stack) {
const [index, parent] = stack;
if (typeof index !== "number" || index === 0) {
return null;
}
return parent.children[index - 1];
return (
isDanglingSpaceSensitiveCssDisplay(node.cssDisplay) &&
!isScriptLikeTag(node)
);
}
function replaceNewlines(text, replacement) {
@ -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.attribs.lang && !node.attribs.type) ||
node.attribs.type === "text/javascript" ||
node.attribs.type === "text/babel" ||
node.attribs.type === "application/javascript")
) {
return "babylon";
}
if (node.name === "script" && !node.attrMap.src) {
if (
(!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")
) {
return "typescript";
if (
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") {
return "css";
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";
}
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;
}
if (prevNode && prevNode.type === "comment") {
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,

View File

@ -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 => {
if (prop.value.type === "ArrayExpression") {
prop.value.elements[0].quasis.forEach(q => delete q.value);
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") {
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);

View File

@ -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;
}
}
return false;
}
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())
);
}
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;

View File

@ -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
};

View File

@ -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;

View File

@ -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))
}
};

View File

@ -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
}
};

View File

@ -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;

View File

@ -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" ||

View File

@ -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
};

View File

@ -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;

View File

@ -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
};

View File

@ -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 = {
"&lt;": "<",
"&gt;": ">",
"&quot;": '"',
"&amp;": "&",
"&#10;": "\n",
"&#9;": "\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
}
}
};

View File

@ -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"
};

View File

@ -181,11 +181,19 @@ 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"
) {
addLeadingComment(ast, comment);
return;
if (locStart(comment) - locStart(ast) <= 0) {
addLeadingComment(ast, comment);
return;
}
if (locEnd(comment) - locEnd(ast) >= 0) {
addTrailingComment(ast, comment);
return;
}
}
decorateComment(ast, comment, options);

View File

@ -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: {

View File

@ -15,7 +15,6 @@ const internalPlugins = [
require("./language-html"),
require("./language-js"),
require("./language-markdown"),
require("./language-vue"),
require("./language-yaml")
];

View File

@ -21,8 +21,10 @@ class TestComponent {}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@Component({
selector: "app-test",
template: \`<ul> <li>test</li>
</ul>
template: \`
<ul>
<li>test</li>
</ul>
\`,
styles: [
\`
@ -60,8 +62,10 @@ class TestComponent {}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@Component({
selector: "app-test",
template: \`<ul> <li>test</li>
</ul>
template: \`
<ul>
<li>test</li>
</ul>
\`,
styles: [
\`

View File

@ -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
]
`;

View File

@ -0,0 +1 @@
run_spec(__dirname, ["__ng_interpolation"]);

View File

@ -0,0 +1,3 @@
[
advancedSearchService.patientInformationFieldsRow2 && advancedSearchService.patientInformationFieldsRow2.indexOf(advancedSearchService.formElementData.customFieldList[i].customFieldType) !== -1
]

View File

@ -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
]

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,3 @@
run_spec(__dirname, ["angular"]);
run_spec(__dirname, ["angular"], { trailingComma: "es5" });
run_spec(__dirname, ["angular"], { printWidth: 1 });

View File

@ -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 (&#123;&#123;title&#125;&#125;, [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
-->

View File

@ -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>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -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=""/>

View File

@ -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>

View File

@ -1,3 +0,0 @@
<a href="#">Link</a>
<textarea>

View File

@ -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>
`;

View File

@ -0,0 +1 @@
<span><![CDATA[<sender>John Smith</sender>]]></span>

View File

@ -0,0 +1 @@
run_spec(__dirname, ["html"]);

View File

@ -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]
-->
`;

View File

@ -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" });

View File

@ -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]
-->

View File

@ -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`] = `

View File

@ -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>

View File

@ -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>
`;

View File

@ -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>

View File

@ -0,0 +1 @@
run_spec(__dirname, ["html"]);

View File

@ -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,38 +258,41 @@ 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> {
constructor(){
super()
class CommentBox extends React.Component<
{ url: string; pollInterval: number },
CommentData
> {
constructor() {
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>
<CommentList data={this.state.data}/>
<CommentForm onCommentSubmit={handleCommentSubmit} />
</div>
);
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm onCommentSubmit={handleCommentSubmit} />
</div>
);
}
}
</script>

View File

@ -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>

View File

@ -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>
`;

View File

@ -10,3 +10,11 @@
>
<span></span>
<div>
<a href="#123123123123123131231312321312312312312312312312312313123123123123123"
>123123123123</a
>
123123
</div>

View File

@ -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">

View File

@ -0,0 +1,3 @@
123<!--prettier-ignore-->456
<span></span><!--prettier-ignore--><span></span>

View File

@ -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>
`;

View File

@ -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

View File

@ -0,0 +1,2 @@
<app-foo></app-foo>
<app-bar></app-bar>

View File

@ -1,4 +0,0 @@
<div>
<ABC />
<span></span>
</div>

View File

@ -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>

View File

@ -26,3 +26,5 @@
</div>
</div>
<textarea></textarea>
<div><textarea>lorem ipsum</textarea></div>

File diff suppressed because it is too large Load Diff

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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
`;

View File

@ -0,0 +1 @@
run_spec(__dirname, ["html"], { insertPragma: true });

View File

@ -0,0 +1,3 @@
<!-- @prettier -->
Hello World

View File

@ -0,0 +1 @@
Hello World

View File

@ -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>
`;

View File

@ -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

View File

@ -0,0 +1 @@
1 /* block-comment */

View File

@ -0,0 +1 @@
1 // line-comment

View File

@ -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>
`;

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script type="text/markdown">
# hello
+ **foo**
+ __bar__
</script>
</head>
<body></body>
</html>

View File

@ -0,0 +1 @@
run_spec(__dirname, ["html"]);

View File

@ -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>
\`;
`;

View File

@ -0,0 +1 @@
run_spec(__dirname, ["babylon"]);

Some files were not shown because too many files have changed in this diff Show More