Skip to content
This repository has been archived by the owner on Jun 7, 2024. It is now read-only.

Commit

Permalink
feat(core/dfn-validators): add dfn type validators (speced#3700)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcoscaceres committed Jul 22, 2021
1 parent 69edf29 commit 7f0e6d7
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 1 deletion.
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"rollup-plugin-minify-html-literals": "^1.2.6",
"rollup-plugin-terser": "^7.0.2",
"serve": "^12.0.0",
"sniffy-mimetype": "^1.1.1",
"typescript": "^4.3.5",
"vnu-jar": "^21.6.11",
"webidl2": "^24.1.2"
Expand Down
62 changes: 62 additions & 0 deletions src/core/dfn-validators.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { MIMEType } from "./import-maps.js";
import { showError } from "./utils.js";

/**
* Validates MIME types strings.
*
* @type {DefinitionValidator} */
export function validateMimeType(text, type, elem, pluginName) {
try {
// Constructor can throw.
const type = new MIMEType(text);
if (type.toString() !== text) {
throw new Error(`Input doesn't match its canonical form: "${type}".`);
}
} catch (error) {
const msg = `Invalid ${type} "${text}": ${error.message}.`;
const hint =
"Check that the MIME type has both a type and a sub-type, and that it's in a canonical form (e.g., `text/plain`).";
showError(msg, pluginName, { hint, elements: [elem] });
return false;
}
return true;
}

/**
* Validates the names of DOM attribute and elements.
* @param {"attribute" | "element"} type
* @type {DefinitionValidator} */
export function validateDOMName(text, type, elem, pluginName) {
try {
switch (type) {
case "attribute":
document.createAttribute(text);
return true;
case "element":
document.createElement(text);
return true;
}
} catch (err) {
const msg = `Invalid ${type} name "${text}": ${err.message}`;
const hint = `Check that the ${type} name is allowed per the XML's Name production for ${type}.`;
showError(msg, pluginName, { hint, elements: [elem] });
}
return false;
}

/**
* Validates common variable or other named thing in a spec, like event names.
*
* @type {DefinitionValidator}
*/
export function validateCommonName(text, type, elem, pluginName) {
// Check a-z, maybe a dash and letters, case insensitive.
// Also, no spaces.
if (/^[a-z]+(-[a-z]+)*$/i.test(text)) {
return true; // all good
}
const msg = `Invalid ${type} name "${text}"`;
const hint = `Check that the ${type} name is allowed`;
showError(msg, pluginName, { hint, elements: [elem] });
return false;
}
5 changes: 5 additions & 0 deletions src/core/import-maps.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import * as _idb from "../../node_modules/idb/build/esm/index.js";
import * as _webidl2 from "../../node_modules/webidl2/index.js";
import { MIMEType as _MIMEType } from "../../node_modules/sniffy-mimetype/index.js";
import _marked from "../../node_modules/marked/lib/marked.esm.js";
import _pluralize from "../../js/deps/builds/pluralize.js";
import hyperHTML from "../../node_modules/hyperhtml/esm.js";
Expand All @@ -20,3 +21,7 @@ export const marked = _marked;
/** @type {import("pluralize")} */
// @ts-ignore
export const pluralize = _pluralize;

/** @type {import("sniffy-mimetype")} */
// @ts-ignore
export const MIMEType = _MIMEType;
10 changes: 10 additions & 0 deletions src/type-helper.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,13 @@ type PersonExtras = {
class?: string;
href?: string;
};

type DefinitionValidator = (
/** Text to validate. */
text: string,
/** The type of thing being validated. */
type: string,
/** The element from which the validation originated. */
element: HTMLElement,
/** The name of the plugin originating the validation. */
pluginName: string) => boolean;
3 changes: 2 additions & 1 deletion tests/karma.conf.base.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ const files = [
included: false,
},
{
pattern: "node_modules/@(idb|hyperhtml|marked|webidl2)/**/*.js",
pattern:
"node_modules/@(idb|hyperhtml|marked|webidl2|sniffy-mimetype)/**/*.js",
included: false,
},
{
Expand Down
140 changes: 140 additions & 0 deletions tests/spec/core/validators-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import {
validateCommonName,
validateDOMName,
validateMimeType,
} from "../../../src/core/dfn-validators.js";

describe("Core - Validators", () => {
describe("validateDOMName", () => {
it("doesn't generate an error if the element is valid", () => {
const elements = [
"h1",
"feSpecularLighting",
"feGaussianBlur",
"body",
"html",
];
for (const element of elements) {
const dfn = document.createElement("dfn");
const context = `element name: ${element}`;
expect(validateDOMName(element, "element", dfn, "foo/bar"))
.withContext(context)
.toBeTrue();
expect(dfn.classList.contains("respec-offending-element"))
.withContext(context)
.toBeFalse();
}
});

it("generates an error if the element name is not valid", () => {
const elements = ["my element", "crypto$", "🪳", "-something", ""];
for (const element of elements) {
const dfn = document.createElement("dfn");
const context = `element name: ${element}`;
expect(validateDOMName(element, "element", dfn, "foo/bar"))
.withContext(context)
.toBeFalse();
expect(dfn.classList.contains("respec-offending-element"))
.withContext(context)
.toBeTrue();
}
});

it("doesn't generates an error if the attribute name is valid", () => {
const attributes = ["crossorigin", "aria-hidden", "aria-roledescription"];
for (const attribute of attributes) {
const context = `attribute name: ${attribute}`;
const dfn = document.createElement("dfn");
expect(validateDOMName(attribute, "attribute", dfn, "foo/bar"))
.withContext(context)
.toBeTrue();
expect(dfn.classList.contains("respec-offending-element"))
.withContext(context)
.toBeFalse();
}
});

it("generates an error if the attribute name is invalid", () => {
const attributes = ["-crossorigin", "-whatever-", "aria-😇"];
for (const attribute of attributes) {
const context = `attribute name: ${attribute}`;
const dfn = document.createElement("dfn");
expect(validateDOMName(attribute, "attribute", dfn, "foo/bar"))
.withContext(context)
.toBeFalse();
expect(dfn.classList.contains("respec-offending-element"))
.withContext(context)
.toBeTrue();
}
});
});

describe("validateMimeType", () => {
it("generates no error if the mimeType is valid", () => {
const mimeTypes = [
"image/svg+xml",
"application/x-font-woff",
"image/jpeg",
"text/plain",
];
for (const mimeType of mimeTypes) {
const dfn = document.createElement("dfn");
const context = `mimeType: ${mimeType}`;
expect(validateMimeType(mimeType, "mimetype", dfn, "foo/bar"))
.withContext(context)
.toBeTrue();
expect(dfn.classList.contains("respec-offending-element"))
.withContext(context)
.toBeFalse();
}
});

it("generates error if the mimeType is not valid", () => {
const mimeTypes = [
"text\\plain",
"not a mimetype",
"a/b;c=this is quoted;e=f",
];
for (const mimeType of mimeTypes) {
const dfn = document.createElement("dfn");
const context = `invalid mimeType: ${mimeType}`;
expect(validateMimeType(mimeType, "mimetype", dfn, "foo/bar"))
.withContext(context)
.toBeFalse();
expect(dfn.classList.contains("respec-offending-element"))
.withContext(context)
.toBeTrue();
}
});
});

describe("validateCommonName", () => {
it("generates no error if the name is valid", () => {
const names = ["foo", "bar", "baz", "quux"];
for (const name of names) {
const dfn = document.createElement("dfn");
const context = `name: ${name}`;
expect(validateCommonName(name, "event", dfn, "foo/bar"))
.withContext(context)
.toBeTrue();
expect(dfn.classList.contains("respec-offending-element"))
.withContext(context)
.toBeFalse();
}
});

it("it generates an error if the name is not valid", () => {
const names = ["my event", "crypto$", "🪳", "-something"];
for (const name of names) {
const dfn = document.createElement("dfn");
const context = `invalid name: ${name}`;
expect(validateCommonName(name, "event", dfn, "foo/bar"))
.withContext(context)
.toBeFalse();
expect(dfn.classList.contains("respec-offending-element"))
.withContext(context)
.toBeTrue();
}
});
});
});

0 comments on commit 7f0e6d7

Please sign in to comment.