Commit eb3c5934 authored by David Sveningsson's avatar David Sveningsson

feat(meta): add method to query all tags with given property

parent edf05b09
......@@ -3,6 +3,7 @@ import { Source } from "../context";
import { DOMTree } from "../dom";
import { InvalidTokenError } from "../lexer";
import "../matchers";
import { MetaTable } from "../meta";
import { Parser } from "../parser";
import { Reporter } from "../reporter";
import { Rule, RuleOptions } from "../rule";
......@@ -355,7 +356,8 @@ describe("Engine", () => {
expect(rule.init).toHaveBeenCalledWith(
parser,
reporter,
Severity.ERROR
Severity.ERROR,
expect.any(MetaTable)
);
expect(rule.setup).toHaveBeenCalledWith();
expect(rule.name).toEqual("void");
......
......@@ -386,9 +386,10 @@ export class Engine<T extends Parser = Parser> {
parser: Parser,
report: Reporter
): Rule {
const meta = this.config.getMetaTable();
const rule = this.instantiateRule(ruleId, options);
rule.name = rule.name || ruleId;
rule.init(parser, report, severity);
rule.init(parser, report, severity, meta);
/* call setup callback if present */
if (rule.setup) {
......
......@@ -46,6 +46,21 @@ export interface MetaData {
requiredContent?: RequiredContent;
}
export type MetaLookupableProperty =
| "metadata"
| "flow"
| "sectioning"
| "heading"
| "phrasing"
| "embedded"
| "interactive"
| "deprecated"
| "foreign"
| "void"
| "transparent"
| "scriptSupporting"
| "form";
export interface MetaElement extends MetaData {
/* filled internally for reverse lookup */
tagName: string;
......
export { MetaTable } from "./table";
export { MetaElement, MetaData, PropertyExpression } from "./element";
export {
MetaData,
MetaElement,
MetaLookupableProperty,
PropertyExpression,
} from "./element";
export { Validator } from "./validator";
......@@ -305,6 +305,35 @@ describe("MetaTable", () => {
attr: ["foo", /bar/, /baz/],
});
});
describe("getTagsWithProperty()", () => {
it("should return list of all tags with given property enabled", () => {
expect.assertions(2);
const table = new MetaTable();
table.loadFromObject({
foo: mockEntry({
flow: true,
}),
bar: mockEntry({
flow: true,
phrasing: true,
}),
});
expect(table.getTagsWithProperty("flow")).toEqual(["foo", "bar"]);
expect(table.getTagsWithProperty("phrasing")).toEqual(["bar"]);
});
it("should return empty list if nothing matches", () => {
expect.assertions(1);
const table = new MetaTable();
table.loadFromObject({
foo: mockEntry({
flow: true,
}),
});
expect(table.getTagsWithProperty("phrasing")).toEqual([]);
});
});
});
function mockEntry(stub = {}): MetaData {
......
......@@ -10,6 +10,7 @@ import {
MetaData,
MetaDataTable,
MetaElement,
MetaLookupableProperty,
PropertyExpression,
} from "./element";
import { MetaValidationError } from "./validation-error";
......@@ -119,6 +120,15 @@ export class MetaTable {
: null;
}
/**
* Find all tags which has enabled given property.
*/
public getTagsWithProperty(propName: MetaLookupableProperty): string[] {
return Object.entries(this.elements)
.filter(([, entry]) => entry[propName])
.map(([tagName]) => tagName);
}
private addEntry(tagName: string, entry: MetaData): void {
const expanded: MetaElement = Object.assign(
{
......
......@@ -5,6 +5,7 @@ import { Event } from "./event";
import { Parser } from "./parser";
import { Reporter } from "./reporter";
import { Rule, ruleDocumentationUrl } from "./rule";
import { MetaTable } from "./meta";
class MockRule extends Rule {
public setup(): void {
......@@ -15,6 +16,7 @@ class MockRule extends Rule {
describe("rule base class", () => {
let parser: Parser;
let reporter: Reporter;
let meta: MetaTable;
let rule: Rule;
let mockLocation: Location;
let mockEvent: Event;
......@@ -24,10 +26,12 @@ describe("rule base class", () => {
parser.on = jest.fn();
reporter = new Reporter();
reporter.add = jest.fn();
meta = new MetaTable();
meta.loadFromFile("../../elements/html5.json");
rule = new MockRule({});
rule.name = "mock-rule";
rule.init(parser, reporter, Severity.ERROR);
rule.init(parser, reporter, Severity.ERROR, meta);
mockLocation = { filename: "mock-file", offset: 1, line: 1, column: 2 };
mockEvent = {
location: mockLocation,
......@@ -173,6 +177,12 @@ describe("rule base class", () => {
it("documentation() should return null", () => {
expect(rule.documentation()).toBeNull();
});
it("getTagsWithProperty() should lookup properties from metadata", () => {
const spy = jest.spyOn(meta, "getTagsWithProperty");
expect(rule.getTagsWithProperty("form")).toEqual(["form"]);
expect(spy).toHaveBeenCalledWith("form");
});
});
it("ruleDocumentationUrl() should return URL to rule documentation", () => {
......
......@@ -16,6 +16,7 @@ import {
} from "./event";
import { Parser } from "./parser";
import { Reporter } from "./reporter";
import { MetaTable, MetaLookupableProperty } from "./meta";
const homepage = require("../package.json").homepage;
......@@ -33,6 +34,7 @@ export type RuleConstructor = new (options: RuleOptions) => Rule;
export abstract class Rule<T = any> {
private reporter: Reporter;
private parser: Parser;
private meta: MetaTable;
private enabled: boolean; // rule enabled/disabled, irregardless of severity
private severity: number; // rule severity, 0: off, 1: warning 2: error
private event: any;
......@@ -84,6 +86,10 @@ export abstract class Rule<T = any> {
return this.enabled && this.severity >= Severity.WARN;
}
public getTagsWithProperty(propName: MetaLookupableProperty): string[] {
return this.meta.getTagsWithProperty(propName);
}
/**
* Report a new error.
*
......@@ -166,10 +172,16 @@ export abstract class Rule<T = any> {
*
* @hidden
*/
public init(parser: Parser, reporter: Reporter, severity: number): void {
public init(
parser: Parser,
reporter: Reporter,
severity: number,
meta: MetaTable
): void {
this.parser = parser;
this.reporter = reporter;
this.severity = severity;
this.meta = meta;
}
/**
......
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