Commit a680f1d2 authored by David Sveningsson's avatar David Sveningsson

feat(rules): new rule `script-type`

parent 27699ad0
Pipeline #118305640 passed with stages
in 10 minutes and 40 seconds
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs/rules/script-type.md inline validation: correct 1`] = `Array []`;
exports[`docs/rules/script-type.md inline validation: incorrect 1`] = `
Array [
Object {
"errorCount": 3,
"filePath": "inline",
"messages": Array [
Object {
"column": 9,
"context": undefined,
"line": 1,
"message": "\\"type\\" attribute is unnecessary for javascript resources",
"offset": 8,
"ruleId": "script-type",
"selector": "script",
"severity": 2,
"size": 4,
},
Object {
"column": 9,
"context": undefined,
"line": 2,
"message": "\\"type\\" attribute is unnecessary for javascript resources",
"offset": 34,
"ruleId": "script-type",
"selector": "script:nth-child(2)",
"severity": 2,
"size": 4,
},
Object {
"column": 9,
"context": undefined,
"line": 3,
"message": "\\"type\\" attribute is unnecessary for javascript resources",
"offset": 75,
"ruleId": "script-type",
"selector": "script:nth-child(3)",
"severity": 2,
"size": 4,
},
],
"source": "<script type=\\"\\"></script>
<script type=\\"text/javascript\\"></script>
<script type=\\"application/javascript\\"></script>",
"warningCount": 0,
},
]
`;
import HtmlValidate from "../../../src/htmlvalidate";
const markup: { [key: string]: string } = {};
markup["incorrect"] = `<script type=""></script>
<script type="text/javascript"></script>
<script type="application/javascript"></script>`;
markup["correct"] = `<script></script>
<script type="module"></script>
<script type="text/plain"></script>
<script type="text/x-custom"></script>`;
describe("docs/rules/script-type.md", () => {
it("inline validation: incorrect", () => {
const htmlvalidate = new HtmlValidate({"rules":{"script-type":"error"}});
const report = htmlvalidate.validateString(markup["incorrect"]);
expect(report.results).toMatchSnapshot();
});
it("inline validation: correct", () => {
const htmlvalidate = new HtmlValidate({"rules":{"script-type":"error"}});
const report = htmlvalidate.validateString(markup["correct"]);
expect(report.results).toMatchSnapshot();
});
});
---
docType: rule
name: script-type
summary: Require valid type for `<script>`
---
# Require valid type for `<script>` element (`script-type`)
The [HTML5 standard encourages][spec] omitting the `type` attribute when the script is a JavaScript resource and only use it to specify `module` or other non-javascript MIME types.
[spec]: https://html.spec.whatwg.org/multipage/scripting.html#attr-script-type
## Rule details
Examples of **incorrect** code for this rule:
<validate name="incorrect" rules="script-type">
<script type=""></script>
<script type="text/javascript"></script>
<script type="application/javascript"></script>
</validate>
Examples of **correct** code for this rule:
<validate name="correct" rules="script-type">
<script></script>
<script type="module"></script>
<script type="text/plain"></script>
<script type="text/x-custom"></script>
</validate>
......@@ -4859,7 +4859,7 @@ exports[`HTML elements <samp> valid markup 1`] = `Array []`;
exports[`HTML elements <script> invalid markup 1`] = `
Array [
Object {
"errorCount": 4,
"errorCount": 6,
"filePath": "test-files/elements/script-invalid.html",
"messages": Array [
Object {
......@@ -4884,32 +4884,54 @@ Array [
"column": 9,
"context": undefined,
"line": 5,
"message": "\\"type\\" attribute is unnecessary for javascript resources",
"offset": 132,
"ruleId": "script-type",
"selector": "script:nth-child(2)",
"severity": 2,
"size": 4,
},
Object {
"column": 9,
"context": undefined,
"line": 6,
"message": "\\"type\\" attribute is unnecessary for javascript resources",
"offset": 158,
"ruleId": "script-type",
"selector": "script:nth-child(3)",
"severity": 2,
"size": 4,
},
Object {
"column": 9,
"context": undefined,
"line": 9,
"message": "Attribute \\"language\\" is deprecated on <script> element",
"offset": 103,
"offset": 230,
"ruleId": "no-deprecated-attr",
"selector": "script:nth-child(2)",
"selector": "script:nth-child(4)",
"severity": 2,
"size": 8,
},
Object {
"column": 8,
"context": "script",
"line": 8,
"line": 12,
"message": "<script> must not be self-closed",
"offset": 330,
"offset": 457,
"ruleId": "no-self-closing",
"selector": "script:nth-child(3)",
"selector": "script:nth-child(5)",
"severity": 2,
"size": 2,
},
Object {
"column": 8,
"context": undefined,
"line": 8,
"line": 12,
"message": "End tag for <script> must not be omitted",
"offset": 330,
"offset": 457,
"ruleId": "script-element",
"selector": "script:nth-child(3)",
"selector": "script:nth-child(5)",
"severity": 2,
"size": 2,
},
......@@ -4917,6 +4939,10 @@ Array [
"source": "<!-- src attribute cannot be empty -->
<script src=\\"\\"></script>
<!-- type attribute should be omitted when empty or js -->
<script type=\\"\\"></script>
<script type=\\"text/javascript\\"></script>
<!-- deprecated attribute -->
<script language=\\"vbscript\\"></script>
......
......@@ -35,6 +35,7 @@ module.exports = {
"prefer-native-element": "error",
"prefer-tbody": "error",
"script-element": "error",
"script-type": "error",
"svg-focusable": "error",
"unrecognized-char-ref": "error",
void: "off",
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`rule script-type should contain documentation 1`] = `
Object {
"description": "While valid the HTML5 standard encourages authors to omit the type element for JavaScript resources.",
"url": "https://html-validate.org/rules/script-type.html",
}
`;
import HtmlValidate from "../htmlvalidate";
import "../matchers";
import { processAttribute } from "../transform/mocks/attribute";
describe("rule script-type", () => {
let htmlvalidate: HtmlValidate;
beforeAll(() => {
htmlvalidate = new HtmlValidate({
rules: { "script-type": "error" },
});
});
it("should not report when script element has implied type", () => {
const report = htmlvalidate.validateString("<script></script>");
expect(report).toBeValid();
});
it("should not report when script element has module type", () => {
const report = htmlvalidate.validateString(
'<script type="module"></script>'
);
expect(report).toBeValid();
});
it("should not report when script element has non-js type", () => {
const report = htmlvalidate.validateString(
'<script type="text/plain"></script>'
);
expect(report).toBeValid();
});
it("should not report error for dynamic attributes", () => {
const report = htmlvalidate.validateString(
'<script dynamic-type="type">',
null,
{
processAttribute,
}
);
expect(report).toBeValid();
});
it("should report when script element have empty type", () => {
const report = htmlvalidate.validateString('<script type=""></script>');
expect(report).toHaveError(
"script-type",
'"type" attribute is unnecessary for javascript resources'
);
});
it("should report when script element have javascript type", () => {
const report = htmlvalidate.validateString(
'<script type="text/javascript"></script>'
);
expect(report).toHaveError(
"script-type",
'"type" attribute is unnecessary for javascript resources'
);
});
it("should report when script element have legacy javascript type", () => {
const report = htmlvalidate.validateString(
'<script type="text/javascript"></script>'
);
expect(report).toHaveError(
"script-type",
'"type" attribute is unnecessary for javascript resources'
);
});
it("should contain documentation", () => {
expect(htmlvalidate.getRuleDocumentation("script-type")).toMatchSnapshot();
});
});
import { TagCloseEvent } from "../event";
import { Rule, RuleDocumentation, ruleDocumentationUrl } from "../rule";
const javascript = [
"",
"application/ecmascript",
"application/javascript",
"text/ecmascript",
"text/javascript",
];
class ScriptType extends Rule {
public documentation(): RuleDocumentation {
return {
description:
"While valid the HTML5 standard encourages authors to omit the type element for JavaScript resources.",
url: ruleDocumentationUrl(__filename),
};
}
public setup(): void {
this.on("tag:close", (event: TagCloseEvent) => {
const node = event.previous;
if (!node || node.tagName !== "script") {
return;
}
const attr = node.getAttribute("type");
if (!attr || attr.isDynamic) {
return;
}
const value = attr.value.toString();
if (!this.isJavascript(value)) {
return;
}
this.report(
node,
'"type" attribute is unnecessary for javascript resources',
attr.keyLocation
);
});
}
private isJavascript(mime: string): boolean {
const match = mime.match(/^(.*?)(?:\s*;.*)?$/);
return javascript.includes(match[1]);
}
}
module.exports = ScriptType;
<!-- src attribute cannot be empty -->
<script src=""></script>
<!-- type attribute should be omitted when empty or js -->
<script type=""></script>
<script type="text/javascript"></script>
<!-- deprecated attribute -->
<script language="vbscript"></script>
......
......@@ -6,3 +6,9 @@
<!-- integrity attribute -->
<script integrity="sha384-..."></script>
<!-- script should allow module type-->
<script type="module"></script>
<!-- script should allow non-js mimetypes-->
<script type="text/plain"></script>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment