Skip to content
This repository has been archived by the owner on Feb 8, 2024. It is now read-only.

Commit

Permalink
Feat: implement only & ignore (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
SukkaW authored Dec 23, 2021
1 parent a48dbbf commit d9a617a
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 13 deletions.
50 changes: 49 additions & 1 deletion __tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ describe("@swc/register", function() {
setupRegister();

expect(typeof currentHook).toBe("function");
expect(currentOptions).toEqual(defaultOptions);
expect(currentOptions.exts).toEqual(defaultOptions.exts);
expect(currentOptions.ignoreNodeModules).toEqual(defaultOptions.ignoreNodeModules);
expect(typeof currentOptions.matcher).toBe("function");
});

test("unregisters hook correctly", () => {
Expand Down Expand Up @@ -136,4 +138,50 @@ describe("@swc/register", function() {

expect(result).toBe('"use strict";\nrequire("assert");\n');
});

test("ignore node_modules by default", () => {
setupRegister();

expect(currentOptions.matcher('foo.js')).toBe(true);
expect(currentOptions.matcher('node_modules/foo.js')).toBe(false);
expect(currentOptions.matcher('subdir/node_modules/foo.js')).toBe(false);
});

test("only compile file under cwd", () => {
setupRegister();

expect(currentOptions.matcher('foo.js')).toBe(true);
expect(currentOptions.matcher('/foo.js')).toBe(false);
});

test("ignore", () => {
setupRegister({
ignore: [/ignore/, 'bar/*.js']
});

expect(currentOptions.matcher('foo.js')).toBe(true);
expect(currentOptions.matcher('ignore.js')).toBe(false);
expect(currentOptions.matcher('bar/a.js')).toBe(false);
});

test("only", () => {
setupRegister({
only: [/foo/, (filename) => filename.includes('bar')]
});

expect(currentOptions.matcher('foo.js')).toBe(true);
expect(currentOptions.matcher('bar.js')).toBe(true);
expect(currentOptions.matcher('baz.js')).toBe(false);
});

test("ignore & only", () => {
setupRegister({
ignore: [/bar/],
only: [/foo/, (filename) => filename.includes('bar')]
});

expect(currentOptions.matcher('foo.js')).toBe(true);
expect(currentOptions.matcher('bar.js')).toBe(false);
expect(currentOptions.matcher('baz.js')).toBe(false);
});
});
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
},
"dependencies": {
"lodash.clonedeep": "^4.5.0",
"lodash.escaperegexp": "^4.1.2",
"pirates": "^4.0.1",
"source-map-support": "^0.5.13"
},
Expand Down
82 changes: 71 additions & 11 deletions src/node.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as swc from "@swc/core";
import fs from "fs";
import deepClone from "lodash.clonedeep";
import escapeRegExp from "lodash.escaperegexp";
import { escapeRegExp, pathPatternToRegex } from "./util";
import path from "path";
import { addHook } from "pirates";
import sourceMapSupport from "source-map-support";
Expand All @@ -10,15 +10,17 @@ export interface InputOptions extends TransformOptions {
extensions?: string[];
}

/**
* Babel has built-in ignore & only support while @swc/core doesn't. So let's make our own!
*/

export interface TransformOptions extends swc.Options {
only?: FilePattern;
ignore?: FilePattern;
}

/**
* TODO:
*/
export type FilePattern = any;
// https://github.com/babel/babel/blob/7e50ee2d823ebc9e50eb3575beb77666214edf8e/packages/babel-core/src/config/validation/options.ts#L201-L202
export type FilePattern = ReadonlyArray<string | ((filename: string, { dirname }: { dirname: string }) => any) | RegExp>;

const maps: { [src: string]: string } = {};
let transformOpts: TransformOptions = {};
Expand Down Expand Up @@ -89,7 +91,7 @@ function compileHook(code: string, filename: string) {

function hookExtensions(exts: readonly string[]) {
if (piratesRevert) piratesRevert();
piratesRevert = addHook(compileHook, { exts: exts as string[], ignoreNodeModules: false });
piratesRevert = addHook(compileHook, { exts: exts as string[], ignoreNodeModules: false, matcher });
}

export function revert() {
Expand Down Expand Up @@ -128,13 +130,71 @@ export default function register(opts: InputOptions = {}) {
// Ignore any node_modules inside the current working directory.
new RegExp(
"^" +
escapeRegExp(cwd) +
"(?:" +
path.sep +
".*)?" +
escapeRegExp(path.sep + "node_modules" + path.sep),
escapeRegExp(cwd) +
"(?:" +
path.sep +
".*)?" +
escapeRegExp(path.sep + "node_modules" + path.sep),
"i"
)
];
}
}


/**
* https://github.com/babel/babel/blob/7acc68a86b70c6aadfef28e10e83d0adb2523807/packages/babel-core/src/config/config-chain.ts
*
* Tests if a filename should be ignored based on "ignore" and "only" options.
*/
function matcher(filename: string, dirname?: string) {
if (!dirname) {
dirname = transformOpts.cwd || path.dirname(filename);
}
return shouldCompile(transformOpts.ignore, transformOpts.only, filename, dirname);
}

function shouldCompile(
ignore: FilePattern | undefined | null,
only: FilePattern | undefined | null,
filename: string,
dirname: string,
): boolean {
if (ignore && matchPattern(ignore, dirname, filename)) {
return false;
}
if (only && !matchPattern(only, dirname, filename)) {
return false;
}
return true;
}

/**
* https://github.com/babel/babel/blob/7acc68a86b70c6aadfef28e10e83d0adb2523807/packages/babel-core/src/config/config-chain.ts
*
* Returns result of calling function with filename if pattern is a function.
* Otherwise returns result of matching pattern Regex with filename.
*/
function matchPattern(
patterns: FilePattern,
dirname: string,
pathToTest: string
): boolean {
return patterns.some(pattern => {
if (typeof pattern === "function") {
return Boolean(pattern(pathToTest, { dirname }));
}

if (typeof pathToTest !== "string") {
throw new Error(
`Configuration contains string/RegExp file pattern, but no filename was provided.`,
);
}

if (typeof pattern === "string") {
pattern = pathPatternToRegex(pattern, dirname);
}
return pattern.test(path.resolve(dirname, pathToTest));
});
}

59 changes: 59 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import path from "path";

export function escapeRegExp(string: string): string {
return string.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
}

/**
* Babel <https://babeljs.io/>
* Released under MIT license <https://github.com/babel/babel/blob/main/LICENSE>
*/
const sep = `\\${path.sep}`;
const endSep = `(?:${sep}|$)`;

const substitution = `[^${sep}]+`;

const starPat = `(?:${substitution}${sep})`;
const starPatLast = `(?:${substitution}${endSep})`;

const starStarPat = `${starPat}*?`;
const starStarPatLast = `${starPat}*?${starPatLast}?`;

/**
* https://github.com/babel/babel/blob/7acc68a86b70c6aadfef28e10e83d0adb2523807/packages/babel-core/src/config/pattern-to-regex.ts
*
* Implement basic pattern matching that will allow users to do the simple
* tests with * and **. If users want full complex pattern matching, then can
* always use regex matching, or function validation.
*/
export function pathPatternToRegex(
pattern: string,
dirname: string,
): RegExp {
const parts = path.resolve(dirname, pattern).split(path.sep);

return new RegExp(
[
"^",
...parts.map((part, i) => {
const last = i === parts.length - 1;

// ** matches 0 or more path parts.
if (part === "**") return last ? starStarPatLast : starStarPat;

// * matches 1 path part.
if (part === "*") return last ? starPatLast : starPat;

// *.ext matches a wildcard with an extension.
if (part.indexOf("*.") === 0) {
return (
substitution + escapeRegExp(part.slice(1)) + (last ? endSep : sep)
);
}

// Otherwise match the pattern text.
return escapeRegExp(part) + (last ? endSep : sep);
}),
].join(""),
);
}

0 comments on commit d9a617a

Please sign in to comment.