...
 
Commits (30)
......@@ -35,12 +35,12 @@
"allowExpressions": true
}
],
"@typescript-eslint/explicit-member-accessibility": "off",
"@typescript-eslint/explicit-member-accessibility": "error",
"@typescript-eslint/indent": "off",
"@typescript-eslint/member-delimiter-style": "error",
"@typescript-eslint/no-angle-bracket-type-assertion": "error",
"@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-triple-slash-reference": "error",
"@typescript-eslint/triple-slash-reference": "error",
"@typescript-eslint/no-unused-vars": "warn",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-var-requires": "off",
......@@ -75,6 +75,12 @@
"sonarjs/no-duplicate-string": "off",
"sonarjs/no-identical-functions": "off"
}
},
{
"files": "*.js",
"rules": {
"@typescript-eslint/explicit-function-return-type": "off"
}
}
]
}
......@@ -80,7 +80,8 @@ Docs:
junit: temp/jest.xml
script:
- npm run build:docs
- ./bin/html-validate.js docs/**/*.html public/**/.html
- npm run htmlvalidate -- --config docs/htmlvalidate-templates.json 'docs/**/*.html'
- npm run htmlvalidate -- --config docs/htmlvalidate-public.json 'public/**/*.html'
- npm test -- docs
- git status
- test -z "$(git status --porcelain)" || (echo 'working copy dirty, need to commit updated specs'; exit 1)
......
## Checklist
- [ ] Changelog updated
- [ ] Documentation updated
- [ ] Change covered by a testcase
- [ ] Commit history cleaned (no WIP, fixups, etc)
......
## Checklist
- [ ] Changelog updated
- [ ] Documentation updated
- [ ] Change covered by a testcase
- [ ] Commit history cleaned (no WIP, fixups, etc)
## Checklist
- [ ] Changelog updated
- [ ] Documentation updated
- [ ] Rule added to recommended list
- [ ] Change covered by a testcase
......
......@@ -2,20 +2,38 @@
# [1.4.0](https://gitlab.com/html-validate/html-validate/compare/v1.3.0...v1.4.0) (2019-08-15)
# [1.5.0](https://gitlab.com/html-validate/html-validate/compare/v1.4.0...v1.5.0) (2019-08-17)
### Bug Fixes
* **deps:** update dependency acorn-walk to v7 ([1fe89e0](https://gitlab.com/html-validate/html-validate/commit/1fe89e0))
* **reporter:** fix {error,warning}Count after merging reports ([bc657d0](https://gitlab.com/html-validate/html-validate/commit/bc657d0))
* **reporter:** require {error,warning}Count to be present in Result ([b1306a4](https://gitlab.com/html-validate/html-validate/commit/b1306a4))
* **elements:** <img> must have non-empty src ([8916e19](https://gitlab.com/html-validate/html-validate/commit/8916e19))
* **rules:** change output format of wcag/h37 and element-required-attributes to match ([26f5074](https://gitlab.com/html-validate/html-validate/commit/26f5074))
### Features
* **cli:** add --config to specify a custom configuration file ([87b565f](https://gitlab.com/html-validate/html-validate/commit/87b565f))
* **elements:** <fieldset> requires <legend> ([0bce9dd](https://gitlab.com/html-validate/html-validate/commit/0bce9dd))
* **elements:** <head> requires <title> ([8aaa801](https://gitlab.com/html-validate/html-validate/commit/8aaa801))
* **elements:** src, href, etc attributes cannot be empty ([89c7ac6](https://gitlab.com/html-validate/html-validate/commit/89c7ac6))
* **parser:** include valueLocation in doctype event ([803ddae](https://gitlab.com/html-validate/html-validate/commit/803ddae))
* **rules:** new rule doctype-html ([46061a7](https://gitlab.com/html-validate/html-validate/commit/46061a7))
* **rules:** new rule element-required-content ([664dead](https://gitlab.com/html-validate/html-validate/commit/664dead))
* **rules:** new rule no-style-tag ([a1dff5c](https://gitlab.com/html-validate/html-validate/commit/a1dff5c))
# [1.4.0](https://gitlab.com/html-validate/html-validate/compare/v1.3.0...v1.4.0) (2019-08-15)
### Bug Fixes
- **deps:** update dependency acorn-walk to v7 ([1fe89e0](https://gitlab.com/html-validate/html-validate/commit/1fe89e0))
- **reporter:** fix {error,warning}Count after merging reports ([bc657d0](https://gitlab.com/html-validate/html-validate/commit/bc657d0))
- **reporter:** require {error,warning}Count to be present in Result ([b1306a4](https://gitlab.com/html-validate/html-validate/commit/b1306a4))
### Features
* **cli:** add new --max-warnings flag ([e78a1dc](https://gitlab.com/html-validate/html-validate/commit/e78a1dc))
* **reporter:** add {error,warning}Count summary to Report object ([2bae1d0](https://gitlab.com/html-validate/html-validate/commit/2bae1d0))
- **cli:** add new --max-warnings flag ([e78a1dc](https://gitlab.com/html-validate/html-validate/commit/e78a1dc))
- **reporter:** add {error,warning}Count summary to Report object ([2bae1d0](https://gitlab.com/html-validate/html-validate/commit/2bae1d0))
# [1.3.0](https://gitlab.com/html-validate/html-validate/compare/v1.2.1...v1.3.0) (2019-08-12)
......
......@@ -54,12 +54,18 @@ module.exports = function(grunt) {
},
copy: {
fonts: {
fontawesome: {
expand: true,
cwd: "node_modules/font-awesome/fonts",
src: "*",
dest: "public/assets/fonts/",
},
glyphicons: {
expand: true,
cwd: "node_modules/bootstrap-sass/assets/fonts/bootstrap",
src: "*",
dest: "public/assets/fonts/",
},
favicon: {
expand: true,
cwd: "docs/app",
......
......@@ -10,7 +10,7 @@ Array [
"column": 2,
"context": undefined,
"line": 1,
"message": "<img> is missing required alt attribute",
"message": "<img> is missing required \\"alt\\" attribute",
"offset": 1,
"ruleId": "WCAG/H37",
"severity": 2,
......@@ -23,7 +23,7 @@ Array [
"element": "button",
},
"line": 2,
"message": "<button> is missing required attribute \\"type\\"",
"message": "<button> is missing required \\"type\\" attribute",
"offset": 22,
"ruleId": "element-required-attributes",
"severity": 2,
......
$fa-font-path: "fonts";
$icon-font-path: "fonts/";
@import "font-awesome";
@import "bootstrap";
......
......@@ -29,13 +29,18 @@ Emitted after the parsing has finished loading the DOM tree.
```typescript
{
location: Location,
value: string,
valueLocation: Location,
}
```
Emitted when a doctype is encountered. `value` is the doctype (without
`<doctype` and `>`).
`location` refers to the doctype opening tag and `valueLocation` to the value
(as described above)
## `tag:open`
```typescript
......
......@@ -5,7 +5,7 @@
# Transformers
### `TemplateExtractor`
## `TemplateExtractor`
Extracts templates from javascript sources.
......@@ -17,7 +17,7 @@ const te = TemplateExtractor.fromFilename("my-file.js");
const source = te.extractObjectProperty("template");
```
### Source hooks
## Source hooks
Transformers can add hooks for additional processing by setting `source.hooks`:
......@@ -38,13 +38,13 @@ source.hooks = {
};
```
#### `processAttribute`
### `processAttribute`
Called before an attribute is set on `HtmlElement` and can be used to modify
both the key and value. If the attribute is processed with scripting
(e.g. databinding) the value may be replaced with `DynamicValue`.
#### `processElement`
### `processElement`
Called after element is fully created but before children are parsed. Can be
used to manipluate elements (e.g. add dynamic text from frameworks).
......@@ -66,9 +66,9 @@ module.exports = {
};
```
### Callbacks
## Callbacks
#### `init()`
### `init()`
The `init` callback is called once during validation engine initialization and
can be used to initialize resources required later.
......@@ -78,7 +78,7 @@ file (as the configuration might change) thus this callback can still be called
multiple times. If you need initialization that happens exactly once for any
scenario you can run it in your plugin file global scope.
#### `setup(source: Source, eventhandler: EventHandler)`
### `setup(source: Source, eventhandler: EventHandler)`
Called once per source and can be used to prepare the plugin for validation,
e.g. rules that requires initialization.
......@@ -88,7 +88,7 @@ events](/dev/events.html) (same as rules).
The callback may not manpiulate the source object.
### Configuration presets
## Configuration presets
Plugins can create configuration presets similar to a shared configuration:
......@@ -112,7 +112,7 @@ Users may then extend the preset using `plugin:name`, e.g.:
}
```
### Rules
## Rules
See [writing rules](/dev/writing-rules.html) for details on how to write a rules.
......@@ -145,7 +145,7 @@ This makes the rules accessable as usual when configuring in
}
```
### Extend metadata
## Extend metadata
Plugins can extend the available [element metadata](/usage/elements.html) by
setting `elementSchema` with an additional [json
......
......@@ -56,18 +56,18 @@ class MyRule extends Rule<ContextualData> {
}
```
### API
## API
#### `options: {[key: string]: any}`
### `options: {[key: string]: any}`
Object with all the options passed from the configuration.
#### `on(event: string, callback: (event: Event)): void`
### `on(event: string, callback: (event: Event)): void`
Listen for events. See [events](/dev/events.html) for a full list of available
events and data.
#### `report(node: HtmlElement, message: string, location?: Location, context?: T): void`
### `report(node: HtmlElement, message: string, location?: Location, context?: T): void`
Report a new error.
......
......@@ -44,7 +44,10 @@ module.exports = function renderMarkdown(trimIndentation) {
.replace(/\(.*?\)/g, "")
.replace(/[^\w]+/g, "-");
const id = `${this.options.headerPrefix}${slug}`;
const anchor = level > 1 ? ` <a class="anchorlink" href="#${id}"></a>` : "";
const anchor =
level > 1
? ` <!-- [html-validate-disable-next wcag/h30] --><a class="anchorlink" href="#${id}" aria-hidden="true"></a>`
: "";
return `<h${level} id="${id}">${text}${anchor}</h${level}>`;
};
......
/* eslint-disable @typescript-eslint/no-empty-function */
const path = require("canonical-path");
const packagePath = __dirname;
const Package = require("dgeni").Package;
......
{# <!-- [html-validate-disable attr-case: since nunjucks templating is not stripped it flags the attributes as wrong case] --> #}
{# <!-- [html-validate-disable close-order: marked wraps the content in a <p> which breaks the layout, has a hack it is closed and reopened in this template so the final output is ok] --> #}
</p>
<div class="inline-validation">
<div class="markup"
{%- for attrName, attrValue in doc.validate.attributes %}{$ attrName $}="{$ attrValue $}"{% endfor %}>
......@@ -12,3 +14,4 @@
</div>
{%- endif %}
</div>
<p>
{% extends "base.template.html" %}
{% block content %}
<!-- [html-validate-disable no-dup-id, heading-level: changelog generator generates headings with these errors] -->
{$ doc.description | marked $}
{% endblock %}
{
"extends": ["htmlvalidate:recommended", "htmlvalidate:document"],
"rules": {
"no-trailing-whitespace": "off",
"require-sri": "off"
}
}
{
"extends": ["htmlvalidate:recommended"]
}
......@@ -54,7 +54,7 @@ element is missing.
**Learn more about [writing element metadata](usage/elements.html) for custom
components.**
### Accessibility
### Accessibility tests
Validates technical aspects of <abbr title="accessibility">a11y</abbr>
guidelines such as WCAG 2.1.
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs/rules/doctype-html.md inline validation: correct 1`] = `Array []`;
exports[`docs/rules/doctype-html.md inline validation: incorrect 1`] = `
Array [
Object {
"errorCount": 1,
"filePath": "inline",
"messages": Array [
Object {
"column": 11,
"context": undefined,
"line": 1,
"message": "doctype should be \\"html\\"",
"offset": 10,
"ruleId": "doctype-html",
"severity": 2,
"size": 79,
},
],
"source": "<!DOCTYPE HTML PUBLIC \\"-//W3C//DTD HTML 4.01//EN\\" \\"http://www.w3.org/TR/html4/strict.dtd\\">",
"warningCount": 0,
},
]
`;
exports[`docs/rules/doctype-html.md inline validation: legacy 1`] = `
Array [
Object {
"errorCount": 1,
"filePath": "inline",
"messages": Array [
Object {
"column": 11,
"context": undefined,
"line": 1,
"message": "doctype should be \\"html\\"",
"offset": 10,
"ruleId": "doctype-html",
"severity": 2,
"size": 33,
},
],
"source": "<!DOCTYPE html SYSTEM \\"about:legacy-compat\\">",
"warningCount": 0,
},
]
`;
......@@ -15,7 +15,7 @@ Array [
"element": "input",
},
"line": 1,
"message": "<input> is missing required attribute \\"type\\"",
"message": "<input> is missing required \\"type\\" attribute",
"offset": 1,
"ruleId": "element-required-attributes",
"severity": 2,
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs/rules/element-required-content.md inline validation: correct 1`] = `Array []`;
exports[`docs/rules/element-required-content.md inline validation: incorrect 1`] = `
Array [
Object {
"errorCount": 2,
"filePath": "inline",
"messages": Array [
Object {
"column": 2,
"context": Object {
"missing": "body",
"node": "html",
},
"line": 1,
"message": "<html> element must have <body> as content",
"offset": 1,
"ruleId": "element-required-content",
"severity": 2,
"size": 4,
},
Object {
"column": 6,
"context": Object {
"missing": "title",
"node": "head",
},
"line": 2,
"message": "<head> element must have <title> as content",
"offset": 12,
"ruleId": "element-required-content",
"severity": 2,
"size": 4,
},
],
"source": "<html>
<head>
</head>
</html>",
"warningCount": 0,
},
]
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs/rules/no-style-tag.md inline validation: correct 1`] = `Array []`;
exports[`docs/rules/no-style-tag.md inline validation: incorrect 1`] = `
Array [
Object {
"errorCount": 1,
"filePath": "inline",
"messages": Array [
Object {
"column": 1,
"context": undefined,
"line": 1,
"message": "Use external stylesheet with <link> instead of <style> tag",
"offset": 0,
"ruleId": "no-style-tag",
"severity": 2,
"size": 6,
},
],
"source": "<style>
body {
background-color: hotpink;
}
</style>",
"warningCount": 0,
},
]
`;
import HtmlValidate from "../../../src/htmlvalidate";
const markup: { [key: string]: string } = {};
markup["incorrect"] = `<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">`;
markup["legacy"] = `<!DOCTYPE html SYSTEM "about:legacy-compat">`;
markup["correct"] = `<!DOCTYPE html>`;
describe("docs/rules/doctype-html.md", () => {
it("inline validation: incorrect", () => {
const htmlvalidate = new HtmlValidate({"rules":{"doctype-html":"error"}});
const report = htmlvalidate.validateString(markup["incorrect"]);
expect(report.results).toMatchSnapshot();
});
it("inline validation: legacy", () => {
const htmlvalidate = new HtmlValidate({"rules":{"doctype-html":"error"}});
const report = htmlvalidate.validateString(markup["legacy"]);
expect(report.results).toMatchSnapshot();
});
it("inline validation: correct", () => {
const htmlvalidate = new HtmlValidate({"rules":{"doctype-html":"error"}});
const report = htmlvalidate.validateString(markup["correct"]);
expect(report.results).toMatchSnapshot();
});
});
import HtmlValidate from "../../../src/htmlvalidate";
const markup: { [key: string]: string } = {};
markup["incorrect"] = `<html>
<head>
</head>
</html>`;
markup["correct"] = `<html>
<head>
<title>foo</title>
</head>
<body></body>
</html>`;
describe("docs/rules/element-required-content.md", () => {
it("inline validation: incorrect", () => {
const htmlvalidate = new HtmlValidate({"rules":{"element-required-content":"error"}});
const report = htmlvalidate.validateString(markup["incorrect"]);
expect(report.results).toMatchSnapshot();
});
it("inline validation: correct", () => {
const htmlvalidate = new HtmlValidate({"rules":{"element-required-content":"error"}});
const report = htmlvalidate.validateString(markup["correct"]);
expect(report.results).toMatchSnapshot();
});
});
import HtmlValidate from "../../../src/htmlvalidate";
const markup: { [key: string]: string } = {};
markup["incorrect"] = `<style>
body {
background-color: hotpink;
}
</style>`;
markup["correct"] = `<link rel="stylesheet" src="my-style.css">`;
describe("docs/rules/no-style-tag.md", () => {
it("inline validation: incorrect", () => {
const htmlvalidate = new HtmlValidate({"rules":{"no-style-tag":"error"}});
const report = htmlvalidate.validateString(markup["incorrect"]);
expect(report.results).toMatchSnapshot();
});
it("inline validation: correct", () => {
const htmlvalidate = new HtmlValidate({"rules":{"no-style-tag":"error"}});
const report = htmlvalidate.validateString(markup["correct"]);
expect(report.results).toMatchSnapshot();
});
});
@ngdoc rule
@module rules
@name doctype-html
@summary Require usage of "html" doctype
@description
# Require usage of "html" doctype (`doctype-html`)
HTML5 requires the usage of the `<!DOCTYPE html>` doctype to prevent browsers
from guessing and/or using "quirks mode".
HTML5 also supports legacy strings for document generators but this rule
disallows legacy strings as well.
This rule only validates the doctype itself not the presence of the
declaration. Use [missing-doctype](missing-doctype.html) to validate presence.
## Rule details
Examples of **incorrect** code for this rule:
<validate name="incorrect" rules="doctype-html">
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
</validate>
<validate name="legacy" rules="doctype-html">
<!DOCTYPE html SYSTEM "about:legacy-compat">
</validate>
Examples of **correct** code for this rule:
<validate name="correct" rules="doctype-html">
<!DOCTYPE html>
</validate>
@ngdoc rule
@module rules
@name element-required-content
@category content-model
@summary Ensure required elements are present
@description
# Ensure required elements are present (`element-required-content`)
Some elements has requirements where certain child elements has to be present.
The requirements comes from the [element metadata](/usage/elements.html):
```js
{
"my-element": {
"requiredContent": ["my-other-element"]
}
}
```
## Rule details
Examples of **incorrect** code for this rule:
<validate name="incorrect" rules="element-required-content">
<html>
<head>
</head>
</html>
</validate>
Examples of **correct** code for this rule:
<validate name="correct" rules="element-required-content">
<html>
<head>
<title>foo</title>
</head>
<body></body>
</html>
</validate>
@ngdoc rule
@module rules
@name no-style-tag
@summary Disallow usage of <style> tag
@description
# Disallow usage of `<style>` tag (`no-style-tag`)
The `<style>` tag can be used to write CSS directly inside the document. When
using multiple documents it is preferable to put all styling in a single asset
and use the `<link>` tag to reference it to lower the bandwidth required (by
preventing duplicated style accross all page loads).
## Rule details
Examples of **incorrect** code for this rule:
<validate name="incorrect" rules="no-style-tag">
<style>
body {
background-color: hotpink;
}
</style>
</validate>
Examples of **correct** code for this rule:
<validate name="correct" rules="no-style-tag">
<link rel="stylesheet" src="my-style.css">
</validate>
......@@ -16,7 +16,7 @@ Array [
"column": 2,
"context": undefined,
"line": 1,
"message": "<img> is missing required alt attribute",
"message": "<img> is missing required \\"alt\\" attribute",
"offset": 1,
"ruleId": "WCAG/H37",
"severity": 2,
......
......@@ -17,7 +17,7 @@ or be excluded from accessibility tree with `role="presentation"` or
`aria-hidden="true"`.
[1]: https://html.spec.whatwg.org/#alt
[2]: https://www.w3.org/TR/WCAG21-TECHS/H37.html
[2]: https://www.w3.org/WAI/WCAG21/Techniques/html/H37
## Rule details
......
......@@ -47,6 +47,7 @@ export interface MetaElement {
permittedDescendants: Permitted;
permittedOrder: PermittedOrder;
requiredAncestors: string[];
requiredContent: string[];
}
```
......@@ -320,6 +321,27 @@ present.
This is used by
[element-permitted-content](/rules/element-permitted-content.html) rule.
### `requiredContent`
Requires certain content in an element.
Some elements has requirements of what content must be present. For instance,
the `<head>` element requires a `<title>` element.
`requiredContent` is a list of tagnames which must be present as a direct
descentant of the element.
```js
"head": {
"requiredContent": [
"title"
]
}
```
This is used by [element-required-content](/rules/element-required-content.html)
rule.
## Global element
The special `*` element can be used to assign global metadata applying to all
......
......@@ -77,7 +77,7 @@ textual description of the content. E.g. it cannot suggest to use `<abbr>` or
</tr>
<tr>
<td class="table-right">H37</td>
<td>Using alt attributes on img elements.<em>Use {@link rule:wcag/h37} to validate. Only checks for presence of text or explicit `"alt=""` for decorative images.</em></td>
<td>Using alt attributes on img elements.<em>Use {@link rule:wcag/h37} to validate. Only checks for presence of text or explicit <code>alt=""</code> for decorative images.</em></td>
<td class="support-yes">Yes</td>
</tr>
<tr>
......@@ -102,7 +102,7 @@ textual description of the content. E.g. it cannot suggest to use `<abbr>` or
</tr>
<tr>
<td class="table-right">H44</td>
<td>Using label elements to associate text labels with form controls.<em>Use {@link rule:input-missing-label} to validate. Rule is only enabled by default in document mode (`htmlvalidate:document`).</em></td>
<td>Using label elements to associate text labels with form controls.<em>Use {@link rule:input-missing-label} to validate. Rule is only enabled by default in document mode (<code>htmlvalidate:document</code>).</em></td>
<td class="support-yes">Yes</td>
</tr>
<tr>
......@@ -147,7 +147,7 @@ textual description of the content. E.g. it cannot suggest to use `<abbr>` or
</tr>
<tr>
<td class="table-right">H57</td>
<td>Using language attributes on the html element.<em>Use {@link rule:element-required-attributes} to validate. `lang` is a required attribute on `<html>` elements.</em></td>
<td>Using language attributes on the html element.<em>Use {@link rule:element-required-attributes} to validate. <code>lang</code> is a required attribute on <code>&lt;html&gt;</code> elements.</em></td>
<td class="support-yes">Yes</td>
</tr>
<tr>
......@@ -172,12 +172,12 @@ textual description of the content. E.g. it cannot suggest to use `<abbr>` or
</tr>
<tr>
<td class="table-right">H63</td>
<td>Using the scope attribute to associate header cells and data cells in data tables.<em>Use {@link rule:element-required-attributes} to validate. `scope` is a required attribute on `<th>` elements.</em></td>
<td>Using the scope attribute to associate header cells and data cells in data tables.<em>Use {@link rule:element-required-attributes} to validate. <code>scope</code> is a required attribute on <code>&gt;th&lt;</code> elements.</em></td>
<td class="support-yes">Yes</td>
</tr>
<tr>
<td class="table-right">H64</td>
<td>Using the title attribute of the frame and iframe elements.<em>Use {@link rule:element-required-attributes} to validate. `title` is a required attribute on `<frame>` and `<iframe>` elements.</em></td>
<td>Using the title attribute of the frame and iframe elements.<em>Use {@link rule:element-required-attributes} to validate. <code>title</code> is a required attribute on <code>&lt;frame&gt;</code> and <code>&lt;iframe&gt;</code> elements.</em></td>
<td class="support-yes">Yes</td>
</tr>
<tr>
......@@ -202,8 +202,8 @@ textual description of the content. E.g. it cannot suggest to use `<abbr>` or
</tr>
<tr>
<td class="table-right">H71</td>
<td>Providing a description for groups of form controls using fieldset and legend elements</td>
<td class="support-planned">Partial planned</td>
<td>Providing a description for groups of form controls using fieldset and legend elements.<em>{@link rule:element-required-content} validates presence of <code>&lt;legend&gt;</code> inside <code>&lt;fieldset&gt;</code>, but not whenever <code>&lt;fieldset&gt;</code> itself is used.</em></td>
<td class="support-yes">Yes</td>
</tr>
<tr>
<td class="table-right">H73</td>
......
This diff is collapsed.
......@@ -285,7 +285,11 @@
"phrasing": true,
"embedded": true,
"interactive": true,
"void": true
"void": true,
"attributes": {
"src": ["/.+/"]
},
"requiredAttributes": ["src", "title"]
},
"fieldset": {
......@@ -295,7 +299,8 @@
},
"deprecatedAttributes": ["datafld"],
"permittedContent": ["@flow", "legend?"],
"permittedOrder": ["legend", "@flow"]
"permittedOrder": ["legend", "@flow"],
"requiredContent": ["legend"]
},
"figcaption": {
......@@ -384,7 +389,8 @@
"head": {
"deprecatedAttributes": ["profile"],
"permittedContent": ["base?", "title?", "@meta"]
"permittedContent": ["base?", "title?", "@meta"],
"requiredContent": ["title"]
},
"header": {
......@@ -407,7 +413,8 @@
"deprecatedAttributes": ["version"],
"permittedContent": ["head?", "body?"],
"permittedOrder": ["head", "body"],
"requiredAttributes": ["lang"]
"requiredAttributes": ["lang"],
"requiredContent": ["head", "body"]
},
"i": {
......@@ -421,6 +428,9 @@
"phrasing": true,
"embedded": true,
"interactive": true,
"attributes": {
"src": ["/.+/"]
},
"deprecatedAttributes": [
"align",
"allowtransparency",
......@@ -446,7 +456,8 @@
"attributes": {
"crossorigin": ["", "anonymous", "use-credentials"],
"decoding": ["sync", "async", "auto"],
"ismap": []
"ismap": [],
"src": ["/.+/"]
},
"deprecatedAttributes": [
"datasrc",
......@@ -457,7 +468,8 @@
"border",
"hspace",
"vspace"
]
],
"requiredAttributes": ["src"]
},
"input": {
......@@ -570,8 +582,10 @@
"void": true,
"attributes": {
"crossorigin": ["", "anonymous", "use-credentials"],
"integrity": ["/.+/"]
"integrity": ["/.+/"],
"href": ["/.+/"]
},
"requiredAttributes": ["href"],
"deprecatedAttributes": ["charset", "methods", "urn", "target"]
},
......@@ -667,6 +681,9 @@
"embedded": true,
"interactive": ["hasAttribute", "usemap"],
"transparent": true,
"attributes": {
"data": ["/.+/"]
},
"deprecatedAttributes": [
"align",
"archive",
......@@ -838,7 +855,8 @@
"crossorigin": ["", "anonymous", "use-credentials"],
"defer": [],
"integrity": ["/.+/"],
"nomodule": []
"nomodule": [],
"src": ["/.+/"]
},
"deprecatedAttributes": ["language", "event", "for"]
},
......
......@@ -34,7 +34,8 @@
"permittedContent": { "$ref": "#/definitions/Permitted" },
"permittedDescendants": { "$ref": "#/definitions/Permitted" },
"permittedOrder": { "$ref": "#/definitions/PermittedOrder" },
"requiredAncestors": { "$ref": "#/definitions/RequiredAncestors" }
"requiredAncestors": { "$ref": "#/definitions/RequiredAncestors" },
"requiredContent": { "$ref": "#/definitions/RequiredContent" }
},
"additionalProperties": false
}
......@@ -131,6 +132,13 @@
"items": {
"type": "string"
}
},
"RequiredContent": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
This diff is collapsed.
{
"name": "html-validate",
"version": "1.4.0",
"version": "1.5.0",
"description": "html linter",
"keywords": [
"html",
......@@ -31,6 +31,7 @@
"clean": "rm -rf build public",
"eslint": "eslint *.js '{docs,elements,src}/**/*.{js,ts}'",
"eslint:fix": "eslint --fix *.js '{docs,elements,src}/**/*.{js,ts}'",
"htmlvalidate": "./bin/html-validate.js",
"lint": "npm run eslint && npm run tslint",
"prettier:check": "prettier '**/*.{ts,js,json,md,scss}' --list-different",
"prettier:write": "prettier '**/*.{ts,js,json,md,scss}' --write",
......@@ -77,6 +78,12 @@
"changelogTitle": "# html-validate changelog\n\n"
}
],
[
"@semantic-release/exec",
{
"prepare": "npm run prettier:write"
}
],
[
"@semantic-release/git",
{
......@@ -102,6 +109,7 @@
"@babel/core": "7.5.5",
"@babel/preset-env": "7.5.5",
"@semantic-release/changelog": "3.0.4",
"@semantic-release/exec": "3.3.6",
"@semantic-release/git": "7.0.16",
"@semantic-release/gitlab": "3.1.7",
"@semantic-release/npm": "5.1.13",
......@@ -113,8 +121,8 @@
"@types/json-merge-patch": "0.0.4",
"@types/minimist": "1.2.0",
"@types/node": "11.13.19",
"@typescript-eslint/eslint-plugin": "1.13.0",
"@typescript-eslint/parser": "1.13.0",
"@typescript-eslint/eslint-plugin": "2.0.0",
"@typescript-eslint/parser": "2.0.0",
"autoprefixer": "9.6.1",
"babelify": "10.0.0",
"bootstrap-sass": "3.4.1",
......@@ -141,8 +149,8 @@
"grunt-sass": "3.1.0",
"highlight.js": "9.15.9",
"husky": "3.0.3",
"jest": "24.8.0",
"jest-diff": "24.8.0",
"jest": "24.9.0",
"jest-diff": "24.9.0",
"jest-junit": "7.0.0",
"jquery": "3.4.1",
"lint-staged": "9.2.1",
......
......@@ -15,4 +15,5 @@ npm run eslint -- --max-warnings 0
npm run tslint
echo Lint documentation
./bin/html-validate.js docs/**/*.html public/**/.html
npm run htmlvalidate -- --config docs/htmlvalidate-templates.json 'docs/**/*.html'
npm run htmlvalidate -- --config docs/htmlvalidate-public.json 'public/**/*.html'
/* eslint-disable no-console, no-process-exit */
/* eslint-disable no-console, no-process-exit, sonarjs/no-duplicate-string */
import { ConfigData } from "../config";
import defaultConfig from "../config/default";
import { TokenDump } from "../engine";
......@@ -42,8 +42,14 @@ function getMode(argv: { [key: string]: any }): Mode {
return Mode.LINT;
}
function getGlobalConfig(rules?: string | string[]): ConfigData {
const config: any = Object.assign({}, defaultConfig);
function getGlobalConfig(
configFile: string,
rules?: string | string[]
): ConfigData {
const baseConfig: ConfigData = configFile
? require(`${process.cwd()}/${configFile}`)
: defaultConfig;
const config: any = Object.assign({}, baseConfig);
if (rules) {
if (Array.isArray(rules)) {
rules = rules.join(",");
......@@ -113,10 +119,18 @@ function renameStdin(report: Report, filename: string): void {
}
const argv: minimist.ParsedArgs = minimist(process.argv.slice(2), {
// eslint-disable-next-line sonarjs/no-duplicate-string
string: ["f", "formatter", "max-warnings", "rule", "stdin-filename"],
string: [
"c",
"config",
"f",
"formatter",
"max-warnings",
"rule",
"stdin-filename",
],
boolean: ["dump-events", "dump-tokens", "dump-tree", "print-config", "stdin"],
alias: {
c: "config",
f: "formatter",
},
default: {
......@@ -143,6 +157,7 @@ Debugging options:
--dump-tree output nodes from the dom tree.
Miscellaneous:
-c, --config=STRING use custom configuration file.
--print-config output configuration for given file.
Formatters:
......@@ -165,7 +180,7 @@ if (argv.h || argv.help || argv._.length === 0) {
}
const mode = getMode(argv);
const config = getGlobalConfig(argv.rule);
const config = getGlobalConfig(argv.config, argv.rule);
const formatter = getFormatter(argv.formatter);
const maxWarnings = parseInt(argv["max-warnings"] || "-1", 10);
const htmlvalidate = new HtmlValidate(config);
......
......@@ -14,12 +14,14 @@ Object {
"close-attr": "error",
"close-order": "error",
"deprecated": "error",
"doctype-html": "error",
"element-case": "error",
"element-name": "error",
"element-permitted-content": "off",
"element-permitted-occurrences": "error",
"element-permitted-order": "error",
"element-required-attributes": "error",
"element-required-content": "error",
"empty-heading": "error",
"empty-title": "error",
"long-title": "error",
......@@ -102,12 +104,14 @@ Object {
"close-attr": "error",
"close-order": "error",
"deprecated": "error",
"doctype-html": "error",
"element-case": "error",
"element-name": "error",
"element-permitted-content": "off",
"element-permitted-occurrences": "error",
"element-permitted-order": "error",
"element-required-attributes": "error",
"element-required-content": "error",
"empty-heading": "error",
"empty-title": "error",
"long-title": "error",
......@@ -221,12 +225,14 @@ Object {
"close-attr": "error",
"close-order": "error",
"deprecated": "error",
"doctype-html": "error",
"element-case": "error",
"element-name": "error",
"element-permitted-content": "off",
"element-permitted-occurrences": "error",
"element-permitted-order": "error",
"element-required-attributes": "error",
"element-required-content": "error",
"empty-heading": "error",
"empty-title": "error",
"long-title": "error",
......@@ -306,12 +312,14 @@ Object {
"close-attr": "error",
"close-order": "error",
"deprecated": "error",
"doctype-html": "error",
"element-case": "error",
"element-name": "error",
"element-permitted-content": "off",
"element-permitted-occurrences": "error",
"element-permitted-order": "error",
"element-required-attributes": "error",
"element-required-content": "error",
"empty-heading": "error",
"empty-title": "error",
"long-title": "error",
......@@ -395,12 +403,14 @@ Object {
"close-attr": "error",
"close-order": "error",
"deprecated": "error",
"doctype-html": "error",
"element-case": "error",
"element-name": "error",
"element-permitted-content": "error",
"element-permitted-occurrences": "error",
"element-permitted-order": "error",
"element-required-attributes": "error",
"element-required-content": "error",
"empty-heading": "error",
"empty-title": "error",
"long-title": "error",
......@@ -483,12 +493,14 @@ Object {
"close-attr": "error",
"close-order": "error",
"deprecated": "error",
"doctype-html": "error",
"element-case": "error",
"element-name": "error",
"element-permitted-content": "error",
"element-permitted-occurrences": "error",
"element-permitted-order": "error",
"element-required-attributes": "error",
"element-required-content": "error",
"empty-heading": "error",
"empty-title": "error",
"long-title": "error",
......@@ -555,12 +567,14 @@ Object {
"close-attr": "error",
"close-order": "error",
"deprecated": "error",
"doctype-html": "error",
"element-case": "error",
"element-name": "error",
"element-permitted-content": "error",
"element-permitted-occurrences": "error",
"element-permitted-order": "error",
"element-required-attributes": "error",
"element-required-content": "error",
"empty-heading": "error",
"empty-title": "error",
"long-title": "error",
......@@ -627,12 +641,14 @@ Object {
"close-attr": "error",
"close-order": "error",
"deprecated": "error",
"doctype-html": "error",
"element-case": "error",
"element-name": "error",
"element-permitted-content": "error",
"element-permitted-occurrences": "error",
"element-permitted-order": "error",
"element-required-attributes": "error",
"element-required-content": "error",
"empty-heading": "error",
"empty-title": "error",
"long-title": "error",
......@@ -676,12 +692,14 @@ Object {
"close-attr": "error",
"close-order": "error",
"deprecated": "error",
"doctype-html": "error",
"element-case": "error",
"element-name": "error",
"element-permitted-content": "error",
"element-permitted-occurrences": "error",
"element-permitted-order": "error",
"element-required-attributes": "error",
"element-required-content": "error",
"empty-heading": "error",
"empty-title": "error",
"long-title": "error",
......
......@@ -23,7 +23,7 @@ export class ConfigLoader {
/**
* @param configClass - Override class to construct.
*/
constructor(configClass: ConfigClass) {
public constructor(configClass: ConfigClass) {
this.cache = new Map<string, Config>();
this.configClass = configClass;
}
......
......@@ -110,7 +110,7 @@ export class Config {
return new Config(defaultConfig);
}
constructor(options?: ConfigData) {
public constructor(options?: ConfigData) {
const initial: ConfigData = {
extends: [],
plugins: [],
......
......@@ -7,12 +7,14 @@ module.exports = {
"close-attr": "error",
"close-order": "error",
deprecated: "error",
"doctype-html": "error",
"element-case": "error",
"element-name": "error",
"element-permitted-content": "error",
"element-permitted-occurrences": "error",
"element-permitted-order": "error",
"element-required-attributes": "error",
"element-required-content": "error",
"empty-heading": "error",
"empty-title": "error",
"long-title": "error",
......
......@@ -33,7 +33,11 @@ export class DOMNode {
* to the tagName but other node types have specific predefined values.
* @param location - Source code location of this node.
*/
constructor(nodeType: NodeType, nodeName: string, location?: Location) {
public constructor(
nodeType: NodeType,
nodeName: string,
location?: Location
) {
this.nodeType = nodeType;
this.nodeName = nodeName || DOCUMENT_NODE_NAME;
this.location = location;
......
......@@ -3,7 +3,7 @@ import { DynamicValue } from "./dynamic-value";
export class DOMTokenList extends Array<string> {
public readonly value: string;
constructor(value: string | DynamicValue) {
public constructor(value: string | DynamicValue) {
if (value && typeof value === "string") {
super(...value.trim().split(/ +/));
} else {
......
......@@ -7,7 +7,7 @@ export class DOMTree {
private active: HtmlElement;
public doctype?: string;
constructor(location: Location) {
public constructor(location: Location) {
this.root = HtmlElement.rootNode(location);
this.active = this.root;
this.doctype = null;
......
export class DynamicValue {
public readonly expr: string;
constructor(expr: string) {
public constructor(expr: string) {
this.expr = expr;
}
......
......@@ -26,7 +26,7 @@ export class HtmlElement extends DOMNode {
public closed: NodeClosed;
protected readonly attr: { [key: string]: Attribute[] };
constructor(
public constructor(
tagName: string,
parent?: HtmlElement,
closed: NodeClosed = NodeClosed.EndTag,
......@@ -108,6 +108,7 @@ export class HtmlElement extends DOMNode {
* Implementation of DOM specification of Element.closest(selectors).
*/
public closest(selectors: string): HtmlElement {
/* eslint-disable-next-line @typescript-eslint/no-this-alias */
let node: HtmlElement = this;
while (node) {
if (node.matches(selectors)) {
......@@ -118,6 +119,11 @@ export class HtmlElement extends DOMNode {
return null;
}
/**
* Tests if this element has given tagname.
*
* If passing "*" this test will pass if any tagname is set.
*/
public is(tagName: string): boolean {
return (this.tagName && tagName === "*") || this.tagName === tagName;
}
......@@ -130,6 +136,7 @@ export class HtmlElement extends DOMNode {
*/
public matches(selector: string): boolean {
/* find root element */
/* eslint-disable-next-line @typescript-eslint/no-this-alias */
let root: HtmlElement = this;
while (root.parent) {
root = root.parent;
......@@ -246,7 +253,7 @@ export class HtmlElement extends DOMNode {
* Return a list of all known classes on the element. Dynamic values are
* ignored.
*/
get classList(): DOMTokenList {
public get classList(): DOMTokenList {
if (!this.hasAttribute("class")) {
return new DOMTokenList(null);
}
......@@ -257,20 +264,20 @@ export class HtmlElement extends DOMNode {
return new DOMTokenList(classes);
}
get id(): string {
public get id(): string {
return this.getAttributeValue("id");
}
get siblings(): HtmlElement[] {
public get siblings(): HtmlElement[] {
return this.parent.childElements;
}
get previousSibling(): HtmlElement {
public get previousSibling(): HtmlElement {
const i = this.siblings.findIndex(node => node.unique === this.unique);
return i >= 1 ? this.siblings[i - 1] : null;
}
get nextSibling(): HtmlElement {
public get nextSibling(): HtmlElement {
const i = this.siblings.findIndex(node => node.unique === this.unique);
return i <= this.siblings.length - 2 ? this.siblings[i + 1] : null;
}
......
......@@ -13,7 +13,7 @@ class Matcher {
class