...
 
Commits (11)
# html-validate changelog
# [1.13.0](https://gitlab.com/html-validate/html-validate/compare/v1.12.0...v1.13.0) (2019-10-13)
### Features
- **rules:** support deprecating rules ([de80d96](https://gitlab.com/html-validate/html-validate/commit/de80d96))
# [1.12.0](https://gitlab.com/html-validate/html-validate/compare/v1.11.0...v1.12.0) (2019-10-08)
### Features
......
@ngdoc rule
@module rules
@name deprecated-rule
@summary Disallow usage of deprecated rules
@description
# Disallow usage of deprecated rules (`deprecated-rule`)
Rules (both builtin and from plugins) can be deprecated in later releases.
This rule disallows the usage of deprecated rules.
This diff is collapsed.
{
"name": "html-validate",
"version": "1.12.0",
"version": "1.13.0",
"description": "html linter",
"keywords": [
"html",
......@@ -112,8 +112,8 @@
"minimist": "^1.2.0"
},
"devDependencies": {
"@babel/core": "7.6.2",
"@babel/preset-env": "7.6.2",
"@babel/core": "7.6.4",
"@babel/preset-env": "7.6.3",
"@commitlint/cli": "8.2.0",
"@commitlint/config-conventional": "8.2.0",
"@semantic-release/changelog": "3.0.4",
......@@ -142,7 +142,7 @@
"eslint-config-sidvind": "1.3.2",
"eslint-plugin-array-func": "3.1.3",
"eslint-plugin-import": "2.18.2",
"eslint-plugin-jest": "22.17.0",
"eslint-plugin-jest": "22.19.0",
"eslint-plugin-node": "10.0.0",
"eslint-plugin-prettier": "3.1.1",
"eslint-plugin-security": "1.4.0",
......@@ -161,7 +161,7 @@
"jest-diff": "24.9.0",
"jest-junit": "8.0.0",
"jquery": "3.4.1",
"lint-staged": "9.4.1",
"lint-staged": "9.4.2",
"load-grunt-tasks": "5.1.0",
"minimatch": "3.0.4",
"prettier": "1.18.2",
......@@ -172,7 +172,7 @@
"ts-jest": "24.1.0",
"tslint": "5.20.0",
"tslint-config-prettier": "1.18.0",
"typescript": "3.6.3"
"typescript": "3.6.4"
},
"jest": {
"collectCoverage": true,
......
type RuleSeverity = "off" | "warn" | "error" | number;
export type RuleConfig = Record<
string,
RuleSeverity | [RuleSeverity] | [RuleSeverity, any]
>;
export interface TransformMap {
[key: string]: string;
}
......@@ -49,7 +54,5 @@ export interface ConfigData {
*/
transform?: TransformMap;
rules?: {
[key: string]: RuleSeverity | [RuleSeverity] | [RuleSeverity, any];
};
rules?: RuleConfig;
}
import fs from "fs";
import glob from "glob";
import path from "path";
import { Config, ConfigLoader } from ".";
import { Config, ConfigData, ConfigLoader, RuleConfig } from ".";
import HtmlValidate from "../htmlvalidate";
declare module "./config-data" {
......@@ -179,13 +179,30 @@ describe("ConfigLoader", () => {
loader = new ConfigLoader(Config);
});
/* extract only relevant rules from configuration to avoid bloat when new
* rules are added to recommended config */
function filter(src: Config): ConfigData {
const whitelisted = ["void", "deprecated", "element-permitted-content"];
const data = src.get();
data.rules = Object.keys(data.rules)
.filter(key => whitelisted.includes(key))
.reduce(
(dst, key) => {
dst[key] = data.rules[key];
return dst;
},
{} as RuleConfig
);
return data;
}
const files = glob.sync("test-files/config/**/*.html");
files.forEach((filename: string) => {
it(filename, () => {
const htmlvalidate = new HtmlValidate();
const config = htmlvalidate.getConfigFor(filename);
const report = htmlvalidate.validateFile(filename);
expect(config.get()).toMatchSnapshot();
expect(filter(config)).toMatchSnapshot();
expect(report.results).toMatchSnapshot();
});
});
......
export { Config } from "./config";
export { ConfigData } from "./config-data";
export { ConfigData, RuleConfig } from "./config-data";
export { ConfigLoader } from "./config-loader";
export { Severity } from "./severity";
......@@ -7,6 +7,7 @@ module.exports = {
"close-attr": "error",
"close-order": "error",
deprecated: "error",
"deprecated-rule": "warn",
"doctype-html": "error",
"element-case": "error",
"element-name": "error",
......
......@@ -389,8 +389,8 @@ describe("Engine", () => {
});
it("should load from plugins", () => {
class MyRule {
public init(): void {
class MyRule extends Rule {
public setup(): void {
/* do nothing */
}
}
......@@ -419,6 +419,34 @@ describe("Engine", () => {
expect(rule.name).toEqual("custom/my-rule");
});
it("should handle missing setup callback", () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore: abstract method not implemented, but plugin might be vanilla js so want to handle the case
class MyRule extends Rule {}
/* mock loading of plugins */
(config as any).plugins = [
{
rules: {
"custom/my-rule": MyRule,
},
},
];
const engine: ExposedEngine<Parser> = new ExposedEngine(
config,
MockParser
);
const rule = engine.loadRule(
"custom/my-rule",
Severity.ERROR,
{},
parser,
reporter
);
expect(rule).toBeInstanceOf(MyRule);
});
it("should handle plugin without rules", () => {
/* mock loading of plugins */
(config as any).plugins = [{}];
......
......@@ -65,6 +65,15 @@ export abstract class Rule<T = any> {
this.enabled = enabled;
}
/**
* Returns `true` if rule is deprecated.
*
* Overridden by subclasses.
*/
public get deprecated(): boolean {
return false;
}
/**
* Test if rule is enabled.
*
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`rule deprecated-rule should contain documentation 1`] = `
Object {
"description": "This rule is deprecated and should not be used any longer, consult documentation for further information.",
"url": "https://html-validate.org/rules/deprecated-rule.html",
}
`;
import HtmlValidate from "../htmlvalidate";
import "../matchers";
import { Rule } from "../rule";
class RuleRegular extends Rule {
public setup(): void {
/* do nothing */
}
}
class RuleDeprecated extends Rule {
public get deprecated(): boolean {
return true;
}
public setup(): void {
/* do nothing */
}
}
jest.mock(
"my-plugin",
() => ({
rules: {
"custom/regular": RuleRegular,
"custom/deprecated": RuleDeprecated,
},
}),
{ virtual: true }
);
describe("rule deprecated-rule", () => {
it("should not report error when no rule is deprecated", () => {
const htmlvalidate = new HtmlValidate({
plugins: ["my-plugin"],
rules: {
"custom/regular": "error",
"deprecated-rule": "error",
},
});
const report = htmlvalidate.validateString("<div></div>");
expect(report).toBeValid();
});
it("should report error when a rule is deprecated", () => {
const htmlvalidate = new HtmlValidate({
plugins: ["my-plugin"],
rules: {
"custom/deprecated": "error",
"deprecated-rule": "error",
},
});
const report = htmlvalidate.validateString("<div></div>");
expect(report).toBeInvalid();
expect(report).toHaveError(
"deprecated-rule",
'Usage of deprecated rule "custom/deprecated"'
);
});
it("should contain documentation", () => {
const htmlvalidate = new HtmlValidate();
expect(
htmlvalidate.getRuleDocumentation("deprecated-rule")
).toMatchSnapshot();
});
});
import { Severity } from "../config";
import { ConfigReadyEvent } from "../event";
import { Rule, RuleDocumentation, ruleDocumentationUrl } from "../rule";
class DeprecatedRule extends Rule {
public documentation(): RuleDocumentation {
return {
description:
"This rule is deprecated and should not be used any longer, consult documentation for further information.",
url: ruleDocumentationUrl(__filename),
};
}
public setup(): void {
this.on("config:ready", (event: ConfigReadyEvent) => {
for (const rule of this.getDeprecatedRules(event)) {
if (rule.getSeverity() > Severity.DISABLED) {
this.report(null, `Usage of deprecated rule "${rule.name}"`);
continue;
}
}
});
}
private getDeprecatedRules(event: ConfigReadyEvent): Rule[] {
const rules = Object.values(event.rules);
return rules.filter(rule => rule.deprecated);
}
}
module.exports = DeprecatedRule;