...
 
Commits (11)
save-exact = true
......@@ -2,15 +2,21 @@
/package.json
/package-lock.json
# generated by renovatebot
/.renovaterc.json
# compiled files
/build
/coverage
/public/**/*.html
/public/assets
/public/schemas
/docs/**/__tests__/*.spec.ts
# nunjucks templates
/docs/**/*.html
/docs/dgeni/inline-validate/templates
/docs/dgeni/schema/templates
# will contain errors
/test-files/**/*.html
# local test files
/sites
# html-validate changelog
# [2.19.0](https://gitlab.com/html-validate/html-validate/compare/v2.18.1...v2.19.0) (2020-03-24)
### Bug Fixes
- **meta:** deep merge during inheritance ([85c377d](https://gitlab.com/html-validate/html-validate/commit/85c377d185492407d72fde39bd14d6a80935a56a)), closes [#72](https://gitlab.com/html-validate/html-validate/issues/72)
### Features
- **meta:** implicit inheritance when overriding existing element ([8833a0f](https://gitlab.com/html-validate/html-validate/commit/8833a0fcc9873eee4938619cdae78afa45e48ce5))
## [2.18.1](https://gitlab.com/html-validate/html-validate/compare/v2.18.0...v2.18.1) (2020-03-22)
### Bug Fixes
......
const sass = require("sass");
const serveStatic = require("serve-static");
module.exports = function(grunt) {
module.exports = function (grunt) {
require("load-grunt-tasks")(grunt);
grunt.registerTask("default", ["build"]);
grunt.registerTask("dgeni", "Generate documentation", function() {
grunt.registerTask("dgeni", "Generate documentation", function () {
const Dgeni = require("dgeni");
const done = this.async();
const dgeni = new Dgeni([require("./docs/dgeni")]);
......@@ -97,7 +97,7 @@ module.exports = function(grunt) {
hostname: "localhost",
keepalive: true,
base: "public",
middleware: function() {
middleware: function () {
return [serveStatic("public")];
},
},
......
......@@ -2,7 +2,7 @@ const Package = require("dgeni").Package;
const pkg = new Package("dgeni-bootstrap", []);
pkg.config(function(inlineTagProcessor, getInjectables) {
pkg.config(function (inlineTagProcessor, getInjectables) {
const inlineTagsDefs = getInjectables(require("./inline-tag-defs"));
inlineTagProcessor.inlineTagDefinitions = inlineTagProcessor.inlineTagDefinitions.concat(
inlineTagsDefs
......
......@@ -2,7 +2,7 @@ module.exports = function alertDangerInlineTagDef() {
return {
name: "alert-danger",
description: "Add bootstrap danger alert",
handler: function(doc, tagName, tagDescription) {
handler: function (doc, tagName, tagDescription) {
return `<div class="alert alert-danger"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> ${tagDescription}</div>`;
},
};
......
......@@ -2,7 +2,7 @@ module.exports = function alertInfoInlineTagDef() {
return {
name: "alert-info",
description: "Add bootstrap info alert",
handler: function(doc, tagName, tagDescription) {
handler: function (doc, tagName, tagDescription) {
return `<div class="alert alert-info"><i class="fa fa-info-circle" aria-hidden="true"></i> ${tagDescription}</div>`;
},
};
......
......@@ -2,7 +2,7 @@ module.exports = function alertSucccessInlineTagDef() {
return {
name: "alert-success",
description: "Add bootstrap success alert",
handler: function(doc, tagName, tagDescription) {
handler: function (doc, tagName, tagDescription) {
return `<div class="alert alert-success"><i class="fa fa-check" aria-hidden="true"></i> ${tagDescription}</div>`;
},
};
......
......@@ -2,7 +2,7 @@ module.exports = function alertWarningInlineTagDef() {
return {
name: "alert-warning",
description: "Add bootstrap warning alert",
handler: function(doc, tagName, tagDescription) {
handler: function (doc, tagName, tagDescription) {
return `<div class="alert alert-warning"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> ${tagDescription}</div>`;
},
};
......
......@@ -9,7 +9,7 @@ function prepare(src) {
module.exports = function changelogFileReader() {
return {
name: "changelogFileReader",
getDocs: function(fileInfo) {
getDocs: function (fileInfo) {
return [
{
docType: "changelog",
......
......@@ -3,14 +3,11 @@ const crypto = require("crypto");
module.exports = {
name: "assetHash",
process: asset => {
process: (asset) => {
const filename = `public/${asset}`;
if (fs.existsSync(filename)) {
const data = fs.readFileSync(filename);
const hash = crypto
.createHash("md5")
.update(data)
.digest("hex");
const hash = crypto.createHash("md5").update(data).digest("hex");
return `${asset}?${hash}`;
} else {
console.log(
......
......@@ -9,11 +9,11 @@ module.exports = new Package("highlight", [require("dgeni-packages/nunjucks")])
.factory(require("./filters/highlight"))
/* enable syntax highlighting */
.config(function(renderMarkdown, highlight) {
.config(function (renderMarkdown, highlight) {
renderMarkdown.highlight = highlight;
})
/* add highlight filter */
.config(function(templateEngine, highlightFilter) {
.config(function (templateEngine, highlightFilter) {
templateEngine.filters.push(highlightFilter);
});
......@@ -8,7 +8,7 @@ module.exports = function renderMarkdown(trimIndentation) {
// remove the leading whitespace from the code block before it gets to the
// markdown code render function
renderer.code = function(code, string, language) {
renderer.code = function (code, string, language) {
const trimmedCode = trimIndentation(code);
let renderedCode = marked.Renderer.prototype.code.call(
this,
......@@ -38,7 +38,7 @@ module.exports = function renderMarkdown(trimIndentation) {
};
// Add § link to all headings
renderer.heading = function(text, level, raw) {
renderer.heading = function (text, level, raw) {
const slug = raw
.toLowerCase()
.replace(/\(.*?\)/g, "")
......@@ -51,7 +51,7 @@ module.exports = function renderMarkdown(trimIndentation) {
return `<h${level} id="${id}">${text}${anchor}</h${level}>`;
};
const render = function(content) {
const render = function (content) {
return marked(content, {
highlight: render.highlight,
renderer,
......
......@@ -13,13 +13,13 @@ module.exports = new Package("html-validate-docs", [
.processor(require("./processors/rules"))
.config(function(renderDocsProcessor) {
.config(function (renderDocsProcessor) {
renderDocsProcessor.extraData.pkg = require("../../package.json");
renderDocsProcessor.extraData.tracking = process.env.GA_TRACKING_ID;
})
/* configure markdown syntax highlighting */
.config(function(highlight) {
.config(function (highlight) {
highlight.configure({
languages: ["js", "json", "typescript", "html", "shell"],
});
......@@ -27,15 +27,15 @@ module.exports = new Package("html-validate-docs", [
.factory(require("./changelog"))
.config(function(readFilesProcessor, changelogFileReader) {
.config(function (readFilesProcessor, changelogFileReader) {
readFilesProcessor.fileReaders.push(changelogFileReader);
})
.config(function(getLinkInfo) {
.config(function (getLinkInfo) {
getLinkInfo.relativeLinks = true;
})
.config(function(log, readFilesProcessor, writeFilesProcessor, copySchema) {
.config(function (log, readFilesProcessor, writeFilesProcessor, copySchema) {
log.level = "info";
readFilesProcessor.basePath = path.resolve(packagePath, "../..");
......@@ -59,14 +59,14 @@ module.exports = new Package("html-validate-docs", [
writeFilesProcessor.outputFolder = "public";
})
.config(function(parseTagsProcessor, getInjectables) {
.config(function (parseTagsProcessor, getInjectables) {
parseTagsProcessor.tagDefinitions = parseTagsProcessor.tagDefinitions.concat(
getInjectables(require("./tag-defs"))
);
})
/* add custom nunjuck filters */
.config(function(templateEngine) {
.config(function (templateEngine) {
templateEngine.filters = templateEngine.filters.concat(
require("./filters")
);
......@@ -74,16 +74,16 @@ module.exports = new Package("html-validate-docs", [
/* add the local template folder first in the search path so it overrides
* dgeni-packages bundled templates */
.config(function(templateFinder) {
.config(function (templateFinder) {
templateFinder.templateFolders.unshift(
path.resolve(packagePath, "templates")
);
})
.config(function(computePathsProcessor, computeIdsProcessor) {
.config(function (computePathsProcessor, computeIdsProcessor) {
computeIdsProcessor.idTemplates.push({
docTypes: ["content", "frontpage", "rule", "rules", "changelog", "error"],
getId: function(doc) {
getId: function (doc) {
const dir = path.dirname(doc.fileInfo.relativePath);
if (dir === ".") {
/* documents not in a subdirectory gets basename as id */
......@@ -98,7 +98,7 @@ module.exports = new Package("html-validate-docs", [
return dir;
}
},
getAliases: function(doc) {
getAliases: function (doc) {
const alias = [doc.id];
if (doc.name) {
alias.push(doc.name);
......@@ -110,7 +110,7 @@ module.exports = new Package("html-validate-docs", [
computePathsProcessor.pathTemplates.push({
docTypes: ["content", "frontpage", "rule", "rules"],
getPath: function(doc) {
getPath: function (doc) {
const dirname = path.dirname(doc.fileInfo.relativePath);
const p = path.join(dirname, doc.fileInfo.baseName);
return `${p}.html`;
......@@ -120,7 +120,7 @@ module.exports = new Package("html-validate-docs", [
computePathsProcessor.pathTemplates.push({
docTypes: ["changelog"],
getPath: function(doc) {
getPath: function (doc) {
const dirname = path.dirname(doc.fileInfo.relativePath);
return path.join(dirname, doc.fileInfo.baseName, "index.html");
},
......@@ -129,7 +129,7 @@ module.exports = new Package("html-validate-docs", [
computePathsProcessor.pathTemplates.push({
docTypes: ["error"],
getPath: function(doc) {
getPath: function (doc) {
/* should go directly under output directory, no subdirectory */
return doc.fileInfo.baseName;
},
......@@ -137,10 +137,10 @@ module.exports = new Package("html-validate-docs", [
});
})
.config(function(checkAnchorLinksProcessor) {
.config(function (checkAnchorLinksProcessor) {
checkAnchorLinksProcessor.ignoredLinks.push(/^\/$/);
checkAnchorLinksProcessor.ignoredLinks.push(/^\/changelog$/);
checkAnchorLinksProcessor.checkDoc = doc => {
checkAnchorLinksProcessor.checkDoc = (doc) => {
return (
doc.path &&
doc.outputPath &&
......
......@@ -15,30 +15,30 @@ module.exports = new Package("inline-validate", [])
.factory(require("./services/validateMap"))
.factory(require("./inline-tag-defs/inline-validation"))
.config(function(templateFinder) {
.config(function (templateFinder) {
templateFinder.templateFolders.push(path.resolve(packagePath, "templates"));
})
.config(function(inlineTagProcessor, inlineValidationInlineTagDef) {
.config(function (inlineTagProcessor, inlineValidationInlineTagDef) {
inlineTagProcessor.inlineTagDefinitions.push(inlineValidationInlineTagDef);
})
.config(function(computePathsProcessor, computeIdsProcessor) {
.config(function (computePathsProcessor, computeIdsProcessor) {
computePathsProcessor.pathTemplates.push({
docTypes: ["validate-config", "validate-markup"],
getPath: function() {},
getPath: function () {},
outputPathTemplate: "inline-validations/${id}",
});
computePathsProcessor.pathTemplates.push({
docTypes: ["validate-spec"],
getPath: function() {},
getPath: function () {},
outputPathTemplate:
"../${fileInfo.path}/__tests__/${fileInfo.file}.spec.ts",
});
computePathsProcessor.pathTemplates.push({
docTypes: ["inlineValidation"],
pathTemplate: "inline-validations/${validate.id}",
getOutputPath: function() {},
getOutputPath: function () {},
});
computeIdsProcessor.idTemplates.push({
docTypes: [
......@@ -47,7 +47,7 @@ module.exports = new Package("inline-validate", [])
"validate-spec",
"inlineValidation",
],
getAliases: function(doc) {
getAliases: function (doc) {
return [doc.id];
},
});
......
......@@ -10,7 +10,7 @@ module.exports = function generateValidationsSpecProcessor(log, validateMap) {
function $process(docs) {
const specs = {};
validateMap.forEach(validation => {
validateMap.forEach((validation) => {
const key = validation.doc.fileInfo.relativePath;
if (!specs[key]) {
......
......@@ -6,7 +6,7 @@ module.exports = function generateInlineValidationsProcessor(log, validateMap) {
};
function $process(docs) {
validateMap.forEach(validation => {
validateMap.forEach((validation) => {
const inlineValidationDoc = createInlineValidateDoc(validation);
docs.push(inlineValidationDoc);
validation.inlineValidationDoc = inlineValidationDoc;
......
......@@ -16,7 +16,7 @@ module.exports = function parseValidatesProcessor(
};
function $process(docs) {
docs.forEach(doc => {
docs.forEach((doc) => {
try {
if (!doc.content) {
return;
......@@ -73,7 +73,7 @@ module.exports = function parseValidatesProcessor(
function extractAttributes(attributeText) {
const attributes = {};
attributeText.replace(ATTRIBUTE_REGEX, function(match, prop, val1, val2) {
attributeText.replace(ATTRIBUTE_REGEX, function (match, prop, val1, val2) {
attributes[prop] = val1 || val2;
});
return attributes;
......
......@@ -13,7 +13,7 @@ module.exports = function generateValidationResultsProcessor(log, validateMap) {
function $process() {
const oldLevel = chalk.level;
chalk.level = 0;
validateMap.forEach(validation => {
validateMap.forEach((validation) => {
htmlvalidate = new HtmlValidate(validation.config);
validation.report = htmlvalidate.validateString(validation.markup);
validation.codeframe = codeframe(validation.report.results);
......
......@@ -27,7 +27,7 @@ module.exports = function rulesProcessor(renderDocsProcessor) {
const ruleDocs = docs.filter(isRuleDocument);
/* compule rule source paths */
ruleDocs.forEach(doc => {
ruleDocs.forEach((doc) => {
const docPath = doc.fileInfo.projectRelativePath;
doc.ruleSourcePath = docPath
.replace("docs", "src")
......@@ -35,7 +35,7 @@ module.exports = function rulesProcessor(renderDocsProcessor) {
});
/* generate title */
ruleDocs.forEach(doc => {
ruleDocs.forEach((doc) => {
if (!doc.title) {
doc.title = `${doc.summary} (${doc.name})`;
}
......@@ -43,7 +43,7 @@ module.exports = function rulesProcessor(renderDocsProcessor) {
/* find all available rules */
const rules = ruleDocs
.map(doc => ({
.map((doc) => ({
name: doc.name,
url: doc.outputPath,
category: doc.category,
......@@ -55,7 +55,7 @@ module.exports = function rulesProcessor(renderDocsProcessor) {
/* group rules into categories */
const categories = {};
rules.forEach(rule => {
rules.forEach((rule) => {
const category = rule.category || "other";
if (!(category in categories)) {
categories[category] = [];
......
......@@ -7,11 +7,15 @@ module.exports = new Package("schema", [])
.processor(require("./processors/copy-schema-processor"))
.factory(require("./services/copy-schema"))
.config(function(computeIdsProcessor, computePathsProcessor, templateFinder) {
.config(function (
computeIdsProcessor,
computePathsProcessor,
templateFinder
) {
templateFinder.templateFolders.push(path.resolve(packagePath, "templates"));
computeIdsProcessor.idTemplates.push({
docTypes: ["schema"],
getAliases: function(doc) {
getAliases: function (doc) {
return [doc.id, doc.name];
},
});
......
module.exports = function() {
module.exports = function () {
return {
name: "category",
};
......
module.exports = function() {
module.exports = function () {
return {
name: "summary",
};
......
......@@ -39,3 +39,5 @@ When inheriting you can still override any properties from the inherited element
For instance <code>&lt;input&gt;</code> is a <code>void</code> element but custom elements cannot be pure <code>void</code> thus if you inherit from it you must set <code>void</code> to <code>false</code>.
</p>
</div>
Inheritance is implied when overwriting existing elements, e.g. when overwriting builtin elements or loading multiple definitions of the same component.
......@@ -20,7 +20,7 @@ In `cypress/plugins/index.js`:
```js
const htmlvalidate = require("cypress-html-validate/dist/plugin");
module.exports = on => {
module.exports = (on) => {
htmlvalidate.install(on);
};
```
......
This diff is collapsed.
{
"name": "html-validate",
"version": "2.18.1",
"version": "2.19.0",
"description": "html linter",
"keywords": [
"html",
......@@ -47,8 +47,8 @@
"eslint": "eslint --ext js,ts .",
"eslint:fix": "eslint --ext js,ts . --fix",
"htmlvalidate": "./bin/html-validate.js",
"prettier:check": "prettier '**/*.{ts,js,json,md,scss}' --list-different",
"prettier:write": "prettier '**/*.{ts,js,json,md,scss}' --write",
"prettier:check": "prettier . --check",
"prettier:write": "prettier . --write",
"semantic-release": "semantic-release",
"start": "grunt connect",
"test": "jest --ci"
......@@ -95,10 +95,10 @@
"@babel/core": "7.9.0",
"@babel/preset-env": "7.9.0",
"@commitlint/cli": "8.3.5",
"@html-validate/commitlint-config": "1.0.1",
"@html-validate/eslint-config": "1.1.4",
"@html-validate/commitlint-config": "1.0.2",
"@html-validate/eslint-config": "1.2.0",
"@html-validate/prettier-config": "1.0.0",
"@html-validate/semantic-release-config": "1.0.11",
"@html-validate/semantic-release-config": "1.0.12",
"@types/babel__code-frame": "7.0.1",
"@types/estree": "0.0.44",
"@types/glob": "7.1.1",
......@@ -136,9 +136,9 @@
"jquery": "3.4.1",
"lint-staged": "10.0.8",
"load-grunt-tasks": "5.1.0",
"marked": "0.8.1",
"marked": "0.8.2",
"minimatch": "3.0.4",
"prettier": "1.19.1",
"prettier": "2.0.2",
"sass": "1.26.3",
"semantic-release": "17.0.4",
"serve-static": "1.14.1",
......
......@@ -30,11 +30,11 @@ function syncMock(pattern: string, options: Options = {}): string[] {
let src = mockFiles;
if (dir) {
src = src
.filter(cur => cur.startsWith(dir))
.map(cur => cur.slice(dir.length));
.filter((cur) => cur.startsWith(dir))
.map((cur) => cur.slice(dir.length));
}
return src.filter(cur => minimatch(cur, pattern));
return src.filter((cur) => minimatch(cur, pattern));
}
glob.setMockFiles = setMockFiles;
......
......@@ -59,7 +59,7 @@ export function expandFiles(
[directoryPattern(extensions)],
Object.assign({}, options, { cwd: fullpath })
);
result = result.concat(dir.map(cur => path.join(filename, cur)));
result = result.concat(dir.map((cur) => path.join(filename, cur)));
continue;
}
......
......@@ -25,7 +25,7 @@ function wrap(
}
export function getFormatter(formatters: string): (report: Report) => string {
const fn: WrappedFormatter[] = formatters.split(",").map(cur => {
const fn: WrappedFormatter[] = formatters.split(",").map((cur) => {
const [name, dst] = cur.split("=", 2);
const moduleName = name.replace(/[^a-z]+/g, "");
/* eslint-disable-next-line import/no-dynamic-require */
......
......@@ -289,10 +289,10 @@ try {
} else if (mode === Mode.INIT) {
cli
.init(process.cwd())
.then(result => {
.then((result) => {
console.log(`Configuration written to "${result.filename}"`);
})
.catch(err => {
.catch((err) => {
if (err) {
console.error(err);
}
......
......@@ -44,7 +44,7 @@ function addFrameworks(src: ConfigData, frameworks: string[]): ConfigData {
function writeConfig(dst: string, config: ConfigData): Promise<void> {
return new Promise((resolve, reject) => {
fs.writeFile(dst, JSON.stringify(config, null, 2), err => {
fs.writeFile(dst, JSON.stringify(config, null, 2), (err) => {
if (err) reject(err);
resolve();
});
......
......@@ -218,7 +218,7 @@ describe("ConfigLoader", () => {
];
const data = src.get();
data.rules = Object.keys(data.rules)
.filter(key => whitelisted.includes(key))
.filter((key) => whitelisted.includes(key))
.reduce((dst, key) => {
dst[key] = data.rules[key];
return dst;
......
......@@ -547,7 +547,7 @@ export class Config {
}
/* try to match an unnamed transformer from plugin */
const plugin = this.plugins.find(cur => cur.name === name);
const plugin = this.plugins.find((cur) => cur.name === name);
if (plugin) {
return this.getUnnamedTransformerFromPlugin(name, plugin);
}
......@@ -566,7 +566,7 @@ export class Config {
pluginName: string,
key: string
): Transformer {
const plugin = this.plugins.find(cur => cur.name === pluginName);
const plugin = this.plugins.find((cur) => cur.name === pluginName);
if (!plugin) {
throw new ConfigError(`No plugin named "${pluginName}" has been loaded`);
}
......
......@@ -52,7 +52,7 @@ export class DOMNode {
* Get the text (recursive) from all child nodes.
*/
public get textContent(): string {
return this.childNodes.map(node => node.textContent).join("");
return this.childNodes.map((node) => node.textContent).join("");
}
public append(node: DOMNode): void {
......
......@@ -42,7 +42,7 @@ export class HtmlElement extends DOMNode {
this.attr = {};
this.metaElement = meta;
this.closed = closed;
this.voidElement = meta ? meta.void : false;
this.voidElement = meta ? Boolean(meta.void) : false;
this.depth = 0;
this.annotation = null;
......@@ -113,7 +113,7 @@ export class HtmlElement extends DOMNode {
*/
public get childElements(): HtmlElement[] {
return this.childNodes.filter(
node => node.nodeType === NodeType.ELEMENT_NODE
(node) => node.nodeType === NodeType.ELEMENT_NODE
) as HtmlElement[];
}
......@@ -164,8 +164,8 @@ export class HtmlElement extends DOMNode {
const parent = cur.parent;
const child = parent.childElements;
const index = child.findIndex(it => it.unique === cur.unique);
const numOfType = child.filter(it => it.is(cur.tagName)).length;
const index = child.findIndex((it) => it.unique === cur.unique);
const numOfType = child.filter((it) => it.is(cur.tagName)).length;
const solo = numOfType === 1;
/* if this is the only tagName in this level of siblings nth-child isn't needed */
......@@ -376,8 +376,8 @@ export class HtmlElement extends DOMNode {
return new DOMTokenList(null);
}
const classes = this.getAttribute("class", true)
.filter(attr => attr.isStatic)
.map(attr => attr.value)
.filter((attr) => attr.isStatic)
.map((attr) => attr.value)
.join(" ");
return new DOMTokenList(classes);
}
......@@ -394,12 +394,12 @@ export class HtmlElement extends DOMNode {
}
public get previousSibling(): HtmlElement {
const i = this.siblings.findIndex(node => node.unique === this.unique);
const i = this.siblings.findIndex((node) => node.unique === this.unique);
return i >= 1 ? this.siblings[i - 1] : null;
}
public get nextSibling(): HtmlElement {
const i = this.siblings.findIndex(node => node.unique === this.unique);
const i = this.siblings.findIndex((node) => node.unique === this.unique);
return i <= this.siblings.length - 2 ? this.siblings[i + 1] : null;
}
......
......@@ -10,7 +10,7 @@ function getNthChild(node: HtmlElement): number {
if (!cache[node.unique]) {
const parent = node.parent;
const index = parent.childElements.findIndex(cur => {
const index = parent.childElements.findIndex((cur) => {
return cur.unique === node.unique;
});
cache[node.unique] = index + 1; /* nthChild starts at 1 */
......
......@@ -183,7 +183,7 @@ export class Selector {
case Combinator.DESCENDANT:
return root.getElementsByTagName(pattern.tagName);
case Combinator.CHILD:
return root.childElements.filter(node => node.is(pattern.tagName));
return root.childElements.filter((node) => node.is(pattern.tagName));
case Combinator.ADJACENT_SIBLING:
return Selector.findAdjacentSibling(root);
case Combinator.GENERAL_SIBLING:
......@@ -196,7 +196,7 @@ export class Selector {
private static findAdjacentSibling(node: HtmlElement): HtmlElement[] {
let adjacent = false;
return node.siblings.filter(cur => {
return node.siblings.filter((cur) => {
if (adjacent) {
adjacent = false;
return true;
......@@ -210,7 +210,7 @@ export class Selector {
private static findGeneralSibling(node: HtmlElement): HtmlElement[] {
let after = false;
return node.siblings.filter(cur => {
return node.siblings.filter((cur) => {
if (after) {
return true;
}
......
......@@ -102,7 +102,7 @@ export class Engine<T extends Parser = Parser> {
parser.on("*", (event, data) => {
lines.push({ event, data });
});
source.forEach(src => parser.parseHtml(src));
source.forEach((src) => parser.parseHtml(src));
return lines;
}
......@@ -195,9 +195,9 @@ export class Engine<T extends Parser = Parser> {
): void {
const rules = event.data
.split(",")
.map(name => name.trim())
.map(name => allRules[name])
.filter(rule => rule); /* filter out missing rules */
.map((name) => name.trim())
.map((name) => allRules[name])
.filter((rule) => rule); /* filter out missing rules */
switch (event.action) {
case "enable":
this.processEnableDirective(rules, parser);
......@@ -231,7 +231,7 @@ export class Engine<T extends Parser = Parser> {
/* enable rules on node */
parser.on("tag:open", (event: string, data: TagOpenEvent) => {
data.target.enableRules(rules.map(rule => rule.name));
data.target.enableRules(rules.map((rule) => rule.name));
});
}
......@@ -242,7 +242,7 @@ export class Engine<T extends Parser = Parser> {
/* disable rules on node */
parser.on("tag:open", (event: string, data: TagOpenEvent) => {
data.target.disableRules(rules.map(rule => rule.name));
data.target.disableRules(rules.map((rule) => rule.name));
});
}
......@@ -262,7 +262,7 @@ export class Engine<T extends Parser = Parser> {
/* disable rules directly on the node so it will be recorded for later,
* more specifically when using the domtree to trigger errors */
data.target.disableRules(rules.map(rule => rule.name));
data.target.disableRules(rules.map((rule) => rule.name));
}
);
......@@ -298,7 +298,7 @@ export class Engine<T extends Parser = Parser> {
const unregister = parser.on(
"tag:open",
(event: string, data: TagOpenEvent) => {
data.target.disableRules(rules.map(rule => rule.name));
data.target.disableRules(rules.map((rule) => rule.name));
}
);
......
......@@ -59,7 +59,7 @@ export class EventHandler {
this.listeners[event] || [],
this.listeners["*"] || []
);
callbacks.forEach(listener => {
callbacks.forEach((listener) => {
listener.call(null, event, data);
});
}
......
......@@ -33,12 +33,12 @@ export default function checkstyleFormatter(results: Result[]): string {
output += `<?xml version="1.0" encoding="utf-8"?>`;
output += `<checkstyle version="4.3">`;
results.forEach(result => {
results.forEach((result) => {
const messages = result.messages;
output += `<file name="${xmlescape(result.filePath)}">`;
messages.forEach(message => {
messages.forEach((message) => {
const ruleId = message.ruleId
? xmlescape(`htmlvalidate.rules.${message.ruleId}`)
: "";
......
......@@ -142,13 +142,13 @@ export default function codeframe(results: Result[]): string {
let warnings = 0;
const resultsWithMessages = results.filter(
result => result.messages.length > 0
(result) => result.messages.length > 0
);
let output = resultsWithMessages
.reduce((resultsOutput, result) => {
const messages = result.messages.map(
message => `${formatMessage(message, result)}\n\n`
(message) => `${formatMessage(message, result)}\n\n`
);
errors += result.errorCount;
......
......@@ -5,7 +5,7 @@ export default function textFormatter(results: Result[]): string {
let output = "";
let total = 0;
results.forEach(result => {
results.forEach((result) => {
const messages = result.messages;
if (messages.length === 0) {
......@@ -15,7 +15,7 @@ export default function textFormatter(results: Result[]): string {
total += messages.length;
output += messages
.map(message => {
.map((message) => {
let messageType;
if (message.severity === 2) {
......
......@@ -97,7 +97,7 @@ class HtmlValidate {
*/
public validateMultipleFiles(filenames: string[]): Report {
return Reporter.merge(
filenames.map(filename => this.validateFile(filename))
filenames.map((filename) => this.validateFile(filename))
);
}
......@@ -185,7 +185,7 @@ class HtmlValidate {
if (source.transformedBy) {
result.push("Transformed by:");
result = result.concat(
source.transformedBy.reverse().map(name => ` - ${name}`)
source.transformedBy.reverse().map((name) => ` - ${name}`)
);
}
if (source.hooks && Object.keys(source.hooks).length > 0) {
......
......@@ -117,7 +117,7 @@ function toHaveErrors(
errors: Array<[string, string] | {}>
): jest.CustomMatcherResult {
const actual = flattenMessages(report);
const matcher = errors.map(entry => {
const matcher = errors.map((entry) => {
if (Array.isArray(entry)) {
const [ruleId, message] = entry;
return expect.objectContaining({ ruleId, message });
......@@ -170,7 +170,7 @@ function toHTMLValidate(
return { pass, message: () => "HTML is valid when an error was expected" };
} else {
const errors = report.results[0].messages.map(
message => ` ${message.message} [${message.ruleId}]`
(message) => ` ${message.message} [${message.ruleId}]`
);
return {
pass,
......
......@@ -406,6 +406,29 @@ describe("MetaTable", () => {
);
});
it("should be implied when a previous element of the same name exists", () => {
expect.assertions(1);
const table = new MetaTable();
table.loadFromObject({
foo: {
flow: true,
},
});
table.loadFromObject({
foo: {
phrasing: true,
},
});
const foo = table.getMetaFor("foo");
expect(foo).toEqual(
expect.objectContaining({
tagName: "foo",
flow: true,
phrasing: true,
})
);
});
it("should allow overriding", () => {
expect.assertions(1);
const table = new MetaTable();
......@@ -429,6 +452,64 @@ describe("MetaTable", () => {
);
});
it("should merge objects", () => {
expect.assertions(2);
const table = new MetaTable();
table.loadFromObject({
foo: {
attributes: {
a: ["1"],
b: ["1"],
c: ["1"],
},
},
bar: {
inherit: "foo",
attributes: {
b: ["2"],
c: null,
},
},
});
table.loadFromObject({
foo: {
attributes: {
a: ["2"],
b: null,
},
},
});
const foo = table.getMetaFor("foo");
const bar = table.getMetaFor("bar");
expect(foo).toMatchInlineSnapshot(`
Object {
"attributes": Object {
"a": Array [
"2",
],
"c": Array [
"1",
],
},
"tagName": "foo",
}
`);
expect(bar).toMatchInlineSnapshot(`
Object {
"attributes": Object {
"a": Array [
"1",
],
"b": Array [
"2",
],
},
"inherit": "foo",
"tagName": "bar",
}
`);
});
it("should throw error when extending missing element", () => {
expect.assertions(1);
const table = new MetaTable();
......
......@@ -37,12 +37,16 @@ function clone(src: any): any {
return JSON.parse(JSON.stringify(src));
}
function overwriteMerge<T>(a: T[], b: T[]): T[] {
return b;
}
/**
* AJV keyword "regexp" to validate the type to be a regular expression.
* Injects errors with the "type" keyword to give the same output.
*/
/* istanbul ignore next: manual testing */
const ajvRegexpValidate: Ajv.ValidateFunction = function(
const ajvRegexpValidate: Ajv.ValidateFunction = function (
data: any,
dataPath: string
): boolean {
......@@ -177,10 +181,7 @@ export class MetaTable {
}
private addEntry(tagName: string, entry: MetaData): void {
const defaultEntry = {
void: false,
};
let parent = {};
let parent = this.elements[tagName] || {};
/* handle inheritance */
if (entry.inherit) {
......@@ -194,9 +195,11 @@ export class MetaTable {
}
/* merge all sources together */
const expanded: MetaElement = Object.assign(defaultEntry, parent, entry, {
tagName,
}) as MetaElement;
const expanded: MetaElement = deepmerge(
parent,
{ ...entry, tagName },
{ arrayMerge: overwriteMerge }
);
expandRegex(expanded);
this.elements[tagName] = expanded;
......@@ -269,7 +272,11 @@ function expandRegexValue(value: string | RegExp): string | RegExp {
function expandRegex(entry: MetaElement): void {
if (!entry.attributes) return;
for (const [name, values] of Object.entries(entry.attributes)) {
entry.attributes[name] = values.map(expandRegexValue);
if (values) {
entry.attributes[name] = values.map(expandRegexValue);
} else {
delete entry.attributes[name];
}
}
}
......@@ -327,7 +334,7 @@ function matchAttribute(node: HtmlElement, match: any): boolean {
`Property expression "matchAttribute" must take [key, op, value] array as argument when evaluating metadata for <${node.tagName}>`
);
}
const [key, op, value] = match.map(x => x.toLowerCase());
const [key, op, value] = match.map((x) => x.toLowerCase());
const nodeValue = (node.getAttributeValue(key) || "").toLowerCase();
switch (op) {
case "!=":
......
......@@ -29,7 +29,7 @@ export class Validator {
if (!rules) {
return true;
}
return rules.some(rule => {
return rules.some((rule) => {
return Validator.validatePermittedRule(node, rule);
});
}
......@@ -54,7 +54,7 @@ export class Validator {
if (!rules) {
return true;
}
const category = rules.find(cur => {
const category = rules.find((cur) => {
/** @todo handle complex rules and not just plain arrays (but as of now
* there is no use-case for it) */
// istanbul ignore next
......@@ -133,7 +133,7 @@ export class Validator {
return true;
}
return rules.some(rule => node.closest(rule));
return rules.some((rule) => node.closest(rule));
}
/**
......@@ -152,8 +152,8 @@ export class Validator {
return [];
}
return rules.filter(tagName => {
const haveMatchingChild = node.childElements.some(child =>
return rules.filter((tagName) => {
const haveMatchingChild = node.childElements.some((child) =>
child.is(tagName)
);
return !haveMatchingChild;
......
......@@ -32,7 +32,6 @@ Object {
"dd",
],
"tagName": "i",
"void": false,
}
`;
......@@ -67,6 +66,5 @@ Object {
],
"phrasing": true,
"tagName": "u",
"void": false,
}
`;
......@@ -1010,7 +1010,7 @@ describe("parser", () => {
it("attribute (deprecated method)", () => {
expect.assertions(5);
const processAttribute = jest.fn(attr => {
const processAttribute = jest.fn((attr) => {
attr.key = "fred";
attr.value = "barney";
return attr;
......@@ -1054,7 +1054,7 @@ describe("parser", () => {
it("by calling hook", () => {
expect.assertions(2);
let context: any;
const processElement = jest.fn(function(this: any) {
const processElement = jest.fn(function (this: any) {
context = this;
});
const source: Source = {
......
......@@ -192,7 +192,6 @@ describe("Plugin", () => {
expect(meta).toEqual({
tagName: "my-element",
myMeta: 5,
void: false,
});
});
......@@ -225,7 +224,6 @@ describe("Plugin", () => {
expect(meta).toEqual({
tagName: "my-element",
myMeta: 5,
void: false,
});
});
......@@ -280,7 +278,6 @@ describe("Plugin", () => {
tagName: "my-element",
foo: "copied" /* foo is marked for copying */,
bar: "original" /* bar is not marked for copying */,
void: false /* autofilled */,
});
});
});
......
......@@ -43,14 +43,16 @@ describe("Reporter", () => {
]);
expect(merged.results).toHaveLength(2);
expect(merged.results[0].filePath).toEqual("foo");
expect(merged.results[0].messages.map(x => x.message)).toEqual([
expect(merged.results[0].messages.map((x) => x.message)).toEqual([
"fred",
"barney",
"wilma",
]);
expect(merged.results[0].errorCount).toEqual(3);
expect(merged.results[1].filePath).toEqual("bar");
expect(merged.results[1].messages.map(x => x.message)).toEqual(["spam"]);
expect(merged.results[1].messages.map((x) => x.message)).toEqual([
"spam",
]);
expect(merged.results[1].errorCount).toEqual(1);
expect(merged.errorCount).toEqual(4);
expect(merged.warningCount).toEqual(0);
......@@ -218,7 +220,7 @@ describe("Reporter", () => {
function createResult(filename: string, messages: string[]): Result {
return {
filePath: filename,
messages: messages.map(cur => createMessage(cur)),
messages: messages.map((cur) => createMessage(cur)),
errorCount: messages.length,
warningCount: 0,
};
......
......@@ -75,7 +75,7 @@ export class Reporter {
* Merge two or more reports into a single one.
*/
public static merge(reports: Report[]): Report {
const valid = reports.every(report => report.valid);
const valid = reports.every((report) => report.valid);
const merged: { [key: string]: Result } = {};
reports.forEach((report: Report) => {
report.results.forEach((result: Result) => {
......@@ -138,7 +138,7 @@ export class Reporter {
public save(sources?: Source[]): Report {
const report: Report = {
valid: this.isValid(),
results: Object.keys(this.result).map(filePath => {
results: Object.keys(this.result).map((filePath) => {
const messages = [].concat(this.result[filePath]).sort(messageSort);
const source = (sources || []).find(
(source: Source) => filePath === (source.filename ?? "")
......@@ -168,11 +168,11 @@ export class Reporter {
}
function countErrors(messages: Message[]): number {
return messages.filter(m => m.severity === Severity.ERROR).length;
return messages.filter((m) => m.severity === Severity.ERROR).length;
}
function countWarnings(messages: Message[]): number {
return messages.filter(m => m.severity === Severity.WARN).length;
return messages.filter((m) => m.severity === Severity.WARN).length;
}
function sumErrors(results: Result[]): number {
......
......@@ -34,7 +34,7 @@ export default class ClassPattern extends Rule<void, RuleOptions> {
}
const classes = new DOMTokenList(event.value);
classes.forEach(cur => {
classes.forEach((cur) => {
if (!cur.match(this.pattern)) {
this.report(
event.target,
......
......@@ -29,6 +29,6 @@ export default class DeprecatedRule extends Rule<string> {
private getDeprecatedRules(event: ConfigReadyEvent): Rule[] {
const rules = Object.values(event.rules);
return rules.filter(rule => rule.deprecated);
return rules.filter((rule) => rule.deprecated);
}
}
......@@ -29,7 +29,7 @@ export default class Deprecated extends Rule<Context> {
text.push(context.documentation);
}
doc.description = text
.map(cur => cur.replace(/\$tagname/g, context.tagName))
.map((cur) => cur.replace(/\$tagname/g, context.tagName))
.join("\n\n");
}
return doc;
......
......@@ -47,7 +47,7 @@ export default class ElementName extends Rule<Context, RuleOptions> {
`<${context.tagName}> is blacklisted by the project configuration.`,
"",
"The following names are blacklisted:",
...context.blacklist.map(cur => `- ${cur}`),
...context.blacklist.map((cur) => `- ${cur}`),
];
}
......
......@@ -39,7 +39,7 @@ export default class ElementPermittedContent extends Rule {
() => this.validatePermittedContent(node, parent, rules),
() => this.validatePermittedDescendant(node, parent),
() => this.validatePermittedAncestors(node),
].some(fn => fn());
].some((fn) => fn());
});
});
}
......
......@@ -24,7 +24,7 @@ export default class ElementPermittedOccurrences extends Rule {
const rules = parent.meta.permittedContent;
const siblings = parent.childElements.filter(
cur => cur.tagName === node.tagName
(cur) => cur.tagName === node.tagName
);
const first = node.unique === siblings[0].unique;
......
......@@ -10,7 +10,7 @@ export default class EmptyTitle extends Rule {
}
public setup(): void {
this.on("tag:close", event => {
this.on("tag:close", (event) => {
const node = event.previous;
if (node.tagName !== "title") return;
......
......@@ -34,11 +34,11 @@ export class CaseStyle {
* Test if a text matches this case style.
*/
public match(text: string): boolean {
return this.styles.some(style => text.match(style.pattern));
return this.styles.some((style) => text.match(style.pattern));
}
public get name(): string {
const names = this.styles.map(style => style.name);
const names = this.styles.map((style) => style.name);
switch (this.styles.length) {
case 1:
return names[0];
......
......@@ -18,12 +18,12 @@ export function classifyNodeText(node: HtmlElement): TextClassification {
const text = findTextNodes(node);
/* if any text is dynamic classify as dynamic */
if (text.some(cur => cur.isDynamic)) {
if (text.some((cur) => cur.isDynamic)) {
return TextClassification.DYNAMIC_TEXT;
}
/* if any text has non-whitespace character classify as static */
if (text.some(cur => cur.textContent.match(/\S/) !== null)) {
if (text.some((cur) => cur.textContent.match(/\S/) !== null)) {
return TextClassification.STATIC_TEXT;
}
......