Commit 6e62852c authored by David Sveningsson's avatar David Sveningsson

feat: allow configuration override when using `validate{String,Source}`

parent 21f252d2
......@@ -83,7 +83,25 @@ describe("HtmlValidate", () => {
const spy = jest.spyOn(htmlvalidate, "getConfigFor");
const str = "foobar";
htmlvalidate.validateString(str, "my-file.html");
expect(spy).toHaveBeenCalledWith("my-file.html");
expect(spy).toHaveBeenCalledWith("my-file.html", undefined);
});
it("should allow overriding configuration", () => {
const mockReport = "mock-report";
engine.lint.mockReturnValue(mockReport);
const htmlvalidate = new HtmlValidate();
const spy = jest.spyOn(htmlvalidate, "getConfigFor");
const str = "foobar";
htmlvalidate.validateString(str, "my-file.html", {
rules: {
deprecated: "off",
},
});
expect(spy).toHaveBeenCalledWith("my-file.html", {
rules: {
deprecated: "off",
},
});
});
});
......@@ -382,10 +400,11 @@ describe("HtmlValidate", () => {
});
describe("getConfigFor()", () => {
it("should load configuration files and merge result", () => {
it("should load configuration files and merge result in correct order", () => {
const htmlvalidate = new HtmlValidate({
rules: {
fred: "error",
a: "error",
b: "error",
},
});
const fromTarget = jest
......@@ -393,7 +412,8 @@ describe("HtmlValidate", () => {
.mockImplementation(() =>
Config.fromObject({
rules: {
barney: "error",
a: "warn",
c: "warn",
},
})
);
......@@ -402,8 +422,46 @@ describe("HtmlValidate", () => {
expect(config.get()).toEqual(
expect.objectContaining({
rules: {
fred: "error",
barney: "error",
a: "warn",
b: "error",
c: "warn",
},
})
);
});
it("should apply configuration override in correct order", () => {
/* constructor global config */
const htmlvalidate = new HtmlValidate({
rules: {
a: "error",
b: "error",
c: "error",
},
});
/* .htmlvalidate.json */
jest
.spyOn((htmlvalidate as any).configLoader, "fromTarget")
.mockImplementation(() =>
Config.fromObject({
rules: {
a: "warn",
b: "warn",
},
})
);
/* override */
const config = htmlvalidate.getConfigFor("my-file.html", {
rules: {
a: "off",
},
});
expect(config.get()).toEqual(
expect.objectContaining({
rules: {
a: "off",
b: "warn",
c: "error",
},
})
);
......@@ -425,6 +483,55 @@ describe("HtmlValidate", () => {
})
);
});
it("should merge global with override when global is root", () => {
const htmlvalidate = new HtmlValidate({
root: true,
rules: {
a: "error",
},
});
const config = htmlvalidate.getConfigFor("my-file.html", {
rules: {
a: "off",
},
});
expect(config.get()).toEqual(
expect.objectContaining({
root: true,
rules: {
a: "off",
},
})
);
});
it("should not load global configuration files if override config is root", () => {
const htmlvalidate = new HtmlValidate({
rules: {
a: "error",
},
});
const fromTarget = jest.spyOn(
(htmlvalidate as any).configLoader,
"fromTarget"
);
const config = htmlvalidate.getConfigFor("my-file.html", {
root: true,
rules: {
b: "error",
},
});
expect(fromTarget).not.toHaveBeenCalled();
expect(config.get()).toEqual(
expect.objectContaining({
root: true,
rules: {
b: "error",
},
})
);
});
});
it("getParserFor() should create a parser for given filename", () => {
......
......@@ -7,6 +7,10 @@ import { Parser } from "./parser";
import { Report, Reporter } from "./reporter";
import { RuleDocumentation } from "./rule";
function isSourceHooks(value: any): value is SourceHooks {
return Boolean(value && (value.processAttribute || value.processElement));
}
/**
* Primary API for using HTML-validate.
*
......@@ -41,8 +45,12 @@ class HtmlValidate {
public validateString(
str: string,
filename?: string,
options?: SourceHooks | ConfigData,
hooks?: SourceHooks
): Report {
if (isSourceHooks(options)) {
return this.validateString(str, filename, null, options);
}
const source = {
data: str,
filename: filename || "inline",
......@@ -51,7 +59,7 @@ class HtmlValidate {
offset: 0,
hooks,
};
return this.validateSource(source);
return this.validateSource(source, options);
}
/**
......@@ -60,8 +68,8 @@ class HtmlValidate {
* @param input - Source to parse.
* @returns Report output.
*/
public validateSource(input: Source): Report {
const config = this.getConfigFor(input.filename);
public validateSource(input: Source, configOverride?: ConfigData): Report {
const config = this.getConfigFor(input.filename, configOverride);
const source = config.transformSource(input);
const engine = new Engine(config, Parser);
return engine.lint(source);
......@@ -239,18 +247,41 @@ class HtmlValidate {
/**
* Get configuration for given filename.
*
* Configuration is read from three sources and in the following order:
*
* 1. Global configuration passed to constructor.
* 2. `.htmlvalidate.json` files found when traversing the directory structure.
* 3. Override passed to this function.
*
* The `root` property set to `true` affects the configuration as following:
*
* 1. If set in override the override is returned as-is.
* 2. If set in the global config the override is merged into global and
* returned. No `.htmlvalidate.json` files are searched.
* 3. Setting `root` in `.htmlvalidate.json` only stops directory traversal.
*
* @param filename - Filename to get configuration for.
* @param configOverride - Configuration to apply last.
*/
public getConfigFor(filename: string): Config {
public getConfigFor(filename: string, configOverride?: ConfigData): Config {
/* special case when the overridden configuration is marked as root, should
* not try to load any more configuration files */
const override = Config.fromObject(configOverride || {});
if (override.isRootFound()) {
override.init();
return override;
}
/* special case when the global configuration is marked as root, should not
* try to load and more configuration files */
if (this.globalConfig.isRootFound()) {
this.globalConfig.init();
return this.globalConfig;
const merged = this.globalConfig.merge(override);
merged.init();
return merged;
}
const config = this.configLoader.fromTarget(filename);
const merged = this.globalConfig.merge(config);
const merged = this.globalConfig.merge(config).merge(override);
merged.init();
return merged;
}
......
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