Commit 9c2112c8 authored by David Sveningsson's avatar David Sveningsson

feat(config): transformers must now operate on `Source`

BREAKING CHANGE: Previously transformers took a filename and had to read data of
the file itself. Transformers will now receive a `Source` instance with the data
preread.
parent 4faf4e25
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`config transform() should throw sane error when transformer fails 1`] = `"When transforming \\"/path/to/test.foo\\": Failed to frobnicate a baz"`;
exports[`config transformSource() should throw sane error when transformer fails 1`] = `"When transforming \\"/path/to/test.foo\\": Failed to frobnicate a baz"`;
exports[`config transform() should throw sane error when transformer fails to load 1`] = `"Failed to load transformer \\"missing-transformer\\""`;
exports[`config transformSource() should throw sane error when transformer fails to load 1`] = `"Failed to load transformer \\"missing-transformer\\""`;
import fs from "fs";
import path from "path";
import { Source } from "../context";
import { UserError } from "../error/user-error";
import { Config } from "./config";
import { Severity } from "./severity";
......@@ -326,7 +327,14 @@ describe("config", () => {
});
});
describe("transform()", () => {
describe("transformSource()", () => {
const source: Source = {
filename: "/path/to/test.foo",
data: "original data",
line: 2,
column: 3,
};
it("should match filename against transformer", () => {
const config = Config.fromObject({
transform: {
......@@ -334,49 +342,48 @@ describe("config", () => {
},
});
config.init();
expect(config.transform("/path/to/test.foo")).toEqual([
expect(config.transformSource(source)).toEqual([
{
data: "mocked source",
filename: "/path/to/test.foo",
line: 1,
column: 1,
originalData: "mocked original source",
originalData: "original data",
},
]);
});
it("should replace <rootDir>", () => {
it("should return original source if no transformer is found", () => {
const config = Config.fromObject({
transform: {
"^.*\\.foo$": "<rootDir>/src/transform/mock",
"^.*\\.bar$": "../transform/mock",
},
});
config.init();
expect(config.transform("/path/to/test.foo")).toEqual([
expect(config.transformSource(source)).toEqual([
{
data: "mocked source",
data: "original data",
filename: "/path/to/test.foo",
line: 1,
column: 1,
originalData: "mocked original source",
line: 2,
column: 3,
},
]);
});
it("should default to reading full file", () => {
it("should replace <rootDir>", () => {
const config = Config.fromObject({
transform: {
"^.*\\.foo$": "../transform/mock",
"^.*\\.foo$": "<rootDir>/src/transform/mock",
},
});
config.init();
expect(config.transform("test-files/parser/simple.html")).toEqual([
expect(config.transformSource(source)).toEqual([
{
data: "<p>Lorem ipsum</p>\n",
filename: "test-files/parser/simple.html",
data: "mocked source",
filename: "/path/to/test.foo",
line: 1,
column: 1,
originalData: "<p>Lorem ipsum</p>\n",
originalData: "original data",
},
]);
});
......@@ -390,7 +397,7 @@ describe("config", () => {
});
config.init();
expect(() =>
config.transform("/path/to/test.foo")
config.transformSource(source)
).toThrowErrorMatchingSnapshot();
});
......@@ -405,6 +412,28 @@ describe("config", () => {
});
});
describe("transformFilename()", () => {
it("should default to reading full file", () => {
const config = Config.fromObject({
transform: {
"^.*\\.foo$": "../transform/mock",
},
});
config.init();
expect(config.transformFilename("test-files/parser/simple.html")).toEqual(
[
{
data: "<p>Lorem ipsum</p>\n",
filename: "test-files/parser/simple.html",
line: 1,
column: 1,
originalData: "<p>Lorem ipsum</p>\n",
},
]
);
});
});
describe("init()", () => {
it("should handle unset fields", () => {
const config = Config.fromObject({
......
......@@ -13,7 +13,7 @@ import { parseSeverity, Severity } from "./severity";
interface Transformer {
pattern: RegExp;
fn: (filename: string) => Source[];
fn: (source: Source) => Source[];
}
const recommended = require("./recommended");
......@@ -321,37 +321,49 @@ export class Config {
}
/**
* Transform a source file.
* Transform a source.
*
* @param filename - Filename to transform (according to configured
* transformations)
* @return A list of extracted sources ready for validation.
* When transforming zero or more new sources will be generated.
*
* @param source - Current source to transform.
* @return A list of transformed sources ready for validation.
*/
public transform(filename: string): Source[] {
const transformer = this.findTransformer(filename);
public transformSource(source: Source): Source[] {
const transformer = this.findTransformer(source.filename);
if (transformer) {
try {
return transformer.fn(filename);
return transformer.fn(source);
} catch (err) {
throw new NestedError(
`When transforming "${filename}": ${err.message}`,
`When transforming "${source.filename}": ${err.message}`,
err
);
}
} else {
const data = fs.readFileSync(filename, { encoding: "utf8" });
return [
{
data,
filename,
line: 1,
column: 1,
originalData: data,
},
];
return [source];
}
}
/**
* Wrapper around [[transformSource]] which reads a file before passing it
* as-is to transformSource.
*
* @param source - Filename to transform (according to configured
* transformations)
* @return A list of transformed sources ready for validation.
*/
public transformFilename(filename: string): Source[] {
const data = fs.readFileSync(filename, { encoding: "utf8" });
const source: Source = {
data,
filename,
line: 1,
column: 1,
originalData: data,
};
return this.transformSource(source);
}
private findTransformer(filename: string): Transformer | null {
return this.transformers.find((entry: Transformer) =>
entry.pattern.test(filename)
......
......@@ -22,7 +22,7 @@ jest.mock("./parser");
function mockConfig(): Config {
const config = Config.empty();
config.init();
config.transform = jest.fn((filename: string) => [
config.transformFilename = jest.fn((filename: string) => [
{
column: 1,
data: `source from ${filename}`,
......@@ -282,7 +282,7 @@ describe("HtmlValidate", () => {
const filename = "foo.html";
const config = Config.empty();
config.init();
config.transform = jest.fn((filename: string) => [
config.transformFilename = jest.fn((filename: string) => [
{
column: 1,
data: `first markup`,
......
......@@ -72,7 +72,7 @@ class HtmlValidate {
*/
public validateFile(filename: string): Report {
const config = this.getConfigFor(filename);
const source = config.transform(filename);
const source = config.transformFilename(filename);
const engine = new Engine(config, Parser);
return engine.lint(source);
}
......@@ -100,7 +100,7 @@ class HtmlValidate {
*/
public dumpTokens(filename: string): TokenDump[] {
const config = this.getConfigFor(filename);
const source = config.transform(filename);
const source = config.transformFilename(filename);
const engine = new Engine(config, Parser);
return engine.dumpTokens(source);
}
......@@ -115,7 +115,7 @@ class HtmlValidate {
*/
public dumpEvents(filename: string): EventDump[] {
const config = this.getConfigFor(filename);
const source = config.transform(filename);
const source = config.transformFilename(filename);
const engine = new Engine(config, Parser);
return engine.dumpEvents(source);
}
......@@ -130,7 +130,7 @@ class HtmlValidate {
*/
public dumpTree(filename: string): string[] {
const config = this.getConfigFor(filename);
const source = config.transform(filename);
const source = config.transformFilename(filename);
const engine = new Engine(config, Parser);
return engine.dumpTree(source);
}
......@@ -145,7 +145,7 @@ class HtmlValidate {
*/
public dumpSource(filename: string): string[] {
const config = this.getConfigFor(filename);
const sources = config.transform(filename);
const sources = config.transformFilename(filename);
return sources.reduce(
(result: string[], source: Source) => {
result.push(
......
import { readFileSync } from "fs";
import glob from "glob";
import { Source } from "./context";
import { DynamicValue } from "./dom";
......@@ -8,25 +7,18 @@ import { AttributeData } from "./parser";
jest.mock(
"mock-transformer",
() => {
return function transformer(filename: string) {
const data = readFileSync(filename, { encoding: "utf-8" });
const source: Source = {
data,
filename,
line: 1,
column: 1,
hooks: {
*processAttribute(attr: AttributeData) {
yield attr;
if (attr.key.startsWith("dynamic-")) {
yield {
key: attr.key.replace("dynamic-", ""),
value: new DynamicValue(attr.value as string),
quote: attr.quote,
originalAttribute: attr.key,
};
}
},
return function transformer(source: Source) {
source.hooks = {
*processAttribute(attr: AttributeData) {
yield attr;
if (attr.key.startsWith("dynamic-")) {
yield {
key: attr.key.replace("dynamic-", ""),
value: new DynamicValue(attr.value as string),
quote: attr.quote,
originalAttribute: attr.key,
};
}
},
};
return [source];
......
import { Source } from "../context";
/**
* Transformer returning a single mocked source.
*/
module.exports = function mockTransform(filename: string) {
module.exports = function mockTransform(source: Source) {
return [
{
data: "mocked source",
filename,
filename: source.filename,
line: 1,
column: 1,
originalData: "mocked original source",
originalData: source.originalData || source.data,
},
];
};
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