Commit e8ef4f5e authored by David Sveningsson's avatar David Sveningsson Committed by David Sveningsson

feat(transform): support `hasChain` to test if a transformer is present

parent d782a7c6
Pipeline #99089326 passed with stages
in 9 minutes and 10 seconds
......@@ -28,14 +28,15 @@ module.exports = myTransform as Transfomer;
```typescript
export interface TransformContext {
hasChain(filename: string): boolean;
chain(source: Source, filename: string): Iterable<Source>;
}
```
#### `chain`
Chain transformations. Sometimes multiple transformers must be applied. For
instance, a Markdown file with JSX in a code-block.
Chain transformations. Sometimes multiple transformers must be applied.
For instance, a Markdown file with JSX in a code-block.
```typescript
for (const source of myTransformation()) {
......@@ -43,8 +44,39 @@ for (const source of myTransformation()) {
}
```
The above snippet will chain transformations using the current transformer
matching `*.foo` files, if it is configured.
The above snippet will chain transformations using the current transformer matching `*.foo` files, if it is configured.
If no pattern matches the filename the input source is returned unmodified.
Use `hasChain` to test if chaining is present.
#### `hasChain`
Test if an additional chainable transformer is present.
Returns true only if there is a transformer configured for the given filename.
While it is always safe to call `chain(..)` as it will passthru sources without a chainable transform it is sometimes desirable to control whenever a `Source` should be yielded or not by determining if the user has configured a transformer or not.
Given a configuration such as:
```js
"transform": {
"^.*\\.foo$": "my-transformer"
"^.*:virtual$": "my-other-transformer"
}
```
`my-transformer` can then implement the following pattern:
```typescript
/* create a virtual filename */
const next = `${source.filename}:virtual`;
if (this.hasChain(next)) {
yield * this.chain(source, next);
}
```
By letting the user configure the `.*:virtual` pattern the user can control whenever `my-transformer` will yield a source for the match or not.
This is useful when the transformer needs to deal with multiple languages and the user should ultimately be able to control whenever a language should be validated by HTML-validate or not.
## `TemplateExtractor`
......
......@@ -499,6 +499,30 @@ describe("config", () => {
`);
});
it("should support testing if chain is present", () => {
const config = Config.fromObject({
transform: {
"^.*\\.foo$": "mock-transform-optional-chain",
"^.*\\.bar$": "mock-transform",
},
});
config.init();
source.filename = "/path/to/test.bar.foo";
expect(config.transformSource(source)).toMatchInlineSnapshot(`
Array [
Object {
"column": 1,
"data": "transformed source (was: data from mock-transform-optional-chain (was: original data))",
"filename": "/path/to/test.bar.foo",
"line": 1,
"originalData": "original data",
},
]
`);
source.filename = "/path/to/test.baz.foo";
expect(config.transformSource(source)).toEqual([]);
});
it("should replace <rootDir>", () => {
const config = Config.fromObject({
transform: {
......
......@@ -341,6 +341,9 @@ export class Config {
public transformSource(source: Source, filename?: string): Source[] {
const transformer = this.findTransformer(filename || source.filename);
const context: TransformContext = {
hasChain: (filename: string): boolean => {
return !!this.findTransformer(filename);
},
chain: (source: Source, filename: string) => {
return this.transformSource(source, filename);
},
......
import { Source } from "../../context";
import { Transformer, TransformContext, TRANSFORMER_API } from "..";
/**
* Mock transformer chaining to a new transformer by chopping of the current
* extension. E.g. "my-file.bar.foo" -> "my-file.bar".
*/
function* mockTransformOptionalChain(
this: TransformContext,
source: Source
): Iterable<Source> {
const next = source.filename.replace(/\.[^.]*$/, "");
if (this.hasChain(next)) {
yield* this.chain(
{
data: `data from mock-transform-optional-chain (was: ${source.data})`,
filename: source.filename,
line: 1,
column: 1,
originalData: source.originalData || source.data,
},
next
);
}
}
/* mocks are always written against current version */
mockTransformOptionalChain.api = TRANSFORMER_API.VERSION;
module.exports = mockTransformOptionalChain as Transformer;
import { Source } from "../context";
export interface TransformContext {
/**
* Test if an additional chainable transformer is present.
*
* Returns true only if there is a transformer configured for the given
* filename.
*
* @param filename - Filename to use to match next transformer.
*/
hasChain(filename: string): boolean;
/**
* Chain transformations.
*
......
......@@ -59,6 +59,7 @@ export function transformSource(
): Source[] {
const defaultChain = (source: Source): Iterable<Source> => [source];
const context: TransformContext = {
hasChain: /* istanbul ignore next */ () => true,
chain: chain || defaultChain,
};
return Array.from(fn.call(context, source));
......
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