Skip to content

Commit

Permalink
Merge pull request #8327 from loganfsmyth/drop-pattern-matching
Browse files Browse the repository at this point in the history
Treat string ignore/only/test/include/exclude values as paths with only basic pattern matching
  • Loading branch information
loganfsmyth committed Jul 16, 2018
2 parents 7446d06 + 6d177ba commit 52a5690
Show file tree
Hide file tree
Showing 12 changed files with 125 additions and 132 deletions.
1 change: 0 additions & 1 deletion packages/babel-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
"debug": "^3.1.0",
"json5": "^0.5.0",
"lodash": "^4.17.5",
"micromatch": "^2.3.11",
"resolve": "^1.3.2",
"semver": "^5.4.1",
"source-map": "^0.5.0"
Expand Down
155 changes: 45 additions & 110 deletions packages/babel-core/src/config/config-chain.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// @flow

import path from "path";
import micromatch from "micromatch";
import buildDebug from "debug";
import {
validate,
Expand All @@ -10,6 +9,7 @@ import {
type ConfigApplicableTest,
type BabelrcSearch,
} from "./validation/options";
import pathPatternToRegex from "./pattern-to-regex";

const debug = buildDebug("babel:config:config-chain");

Expand Down Expand Up @@ -52,11 +52,6 @@ export type ConfigContext = {
envName: string,
};

type ConfigContextNamed = {
...ConfigContext,
filename: string,
};

/**
* Build a config chain for a given preset.
*/
Expand Down Expand Up @@ -217,23 +212,31 @@ function babelrcLoadEnabled(

const absoluteRoot = context.root;

// Fast path to avoid having to load micromatch if the babelrc is just
// Fast path to avoid having to match patterns if the babelrc is just
// loading in the standard root directory.
if (babelrcRoots === undefined) {
return pkgData.directories.indexOf(absoluteRoot) !== -1;
}

let babelrcPatterns = babelrcRoots;
if (!Array.isArray(babelrcPatterns)) babelrcPatterns = [babelrcPatterns];
babelrcPatterns = babelrcPatterns.map(pat => path.resolve(context.cwd, pat));
babelrcPatterns = babelrcPatterns.map(pat => {
return typeof pat === "string" ? path.resolve(context.cwd, pat) : pat;
});

// Fast path to avoid having to load micromatch if the babelrc is just
// Fast path to avoid having to match patterns if the babelrc is just
// loading in the standard root directory.
if (babelrcPatterns.length === 1 && babelrcPatterns[0] === absoluteRoot) {
return pkgData.directories.indexOf(absoluteRoot) !== -1;
}

return micromatch(pkgData.directories, babelrcPatterns).length > 0;
return babelrcPatterns.some(pat => {
if (typeof pat === "string") pat = pathPatternToRegex(pat, context.cwd);

return pkgData.directories.some(directory => {
return matchPattern(pat, context.cwd, directory);
});
});
}

const validateConfigFile = makeWeakCache(
Expand Down Expand Up @@ -583,20 +586,9 @@ function configFieldIsApplicable(
test: ConfigApplicableTest,
dirname: string,
): boolean {
if (typeof context.filename !== "string") {
throw new Error(
`Configuration contains explicit test/include/exclude checks, but no filename was passed to Babel`,
);
}
// $FlowIgnore - Flow refinements aren't quite smart enough for this :(
const ctx: ConfigContextNamed = context;

const patterns = Array.isArray(test) ? test : [test];

// Disabling negation here because it's a bit buggy from
// https://github.com/babel/babel/issues/6907 and it's not clear that it is
// needed since users can use 'exclude' alongside 'test'/'include'.
return matchesPatterns(ctx, patterns, dirname, false /* allowNegation */);
return matchesPatterns(context, patterns, dirname);
}

/**
Expand All @@ -608,43 +600,24 @@ function shouldIgnore(
only: ?IgnoreList,
dirname: string,
): boolean {
if (ignore) {
if (typeof context.filename !== "string") {
throw new Error(
`Configuration contains ignore checks, but no filename was passed to Babel`,
);
}
// $FlowIgnore - Flow refinements aren't quite smart enough for this :(
const ctx: ConfigContextNamed = context;
if (matchesPatterns(ctx, ignore, dirname)) {
debug(
"Ignored %o because it matched one of %O from %o",
context.filename,
ignore,
dirname,
);
return true;
}
if (ignore && matchesPatterns(context, ignore, dirname)) {
debug(
"Ignored %o because it matched one of %O from %o",
context.filename,
ignore,
dirname,
);
return true;
}

if (only) {
if (typeof context.filename !== "string") {
throw new Error(
`Configuration contains ignore checks, but no filename was passed to Babel`,
);
}
// $FlowIgnore - Flow refinements aren't quite smart enough for this :(
const ctx: ConfigContextNamed = context;

if (!matchesPatterns(ctx, only, dirname)) {
debug(
"Ignored %o because it failed to match one of %O from %o",
context.filename,
only,
dirname,
);
return true;
}
if (only && !matchesPatterns(context, only, dirname)) {
debug(
"Ignored %o because it failed to match one of %O from %o",
context.filename,
only,
dirname,
);
return true;
}

return false;
Expand All @@ -655,64 +628,26 @@ function shouldIgnore(
* Otherwise returns result of matching pattern Regex with filename.
*/
function matchesPatterns(
context: ConfigContextNamed,
context: ConfigContext,
patterns: IgnoreList,
dirname: string,
allowNegation?: boolean = true,
): boolean {
const res = [];
const strings = [];
const fns = [];

patterns.forEach(pattern => {
if (typeof pattern === "string") strings.push(pattern);
else if (typeof pattern === "function") fns.push(pattern);
else res.push(pattern);
});

const filename = context.filename;
if (res.some(re => re.test(context.filename))) return true;
if (fns.some(fn => fn(filename))) return true;

if (strings.length > 0) {
const possibleDirs = getPossibleDirs(context);

const absolutePatterns = strings.map(pattern => {
// Preserve the "!" prefix so that micromatch can use it for negation.
const negate = pattern[0] === "!";
if (negate && !allowNegation) {
throw new Error(`Negation of file paths is not supported.`);
}
if (negate) pattern = pattern.slice(1);

return (negate ? "!" : "") + path.resolve(dirname, pattern);
});

if (
micromatch(possibleDirs, absolutePatterns, {
nocase: true,
nonegate: !allowNegation,
}).length > 0
) {
return true;
}
}

return false;
return patterns.some(pattern =>
matchPattern(pattern, dirname, context.filename),
);
}

const getPossibleDirs = makeWeakCache((context: ConfigContextNamed) => {
let current = context.filename;
if (typeof current !== "string") return [];

const possibleDirs = [current];
while (true) {
const previous = current;
current = path.dirname(current);
if (previous === current) break;
function matchPattern(pattern, dirname, pathToTest): boolean {
if (typeof pattern === "function") return !!pattern(pathToTest);

possibleDirs.push(current);
if (typeof pathToTest !== "string") {
throw new Error(
`Configuration contains string/RegExp pattern, but no filename was passed to Babel`,
);
}

return possibleDirs;
});
if (typeof pattern === "string") {
pattern = pathPatternToRegex(pattern, dirname);
}
return pattern.test(pathToTest);
}
14 changes: 12 additions & 2 deletions packages/babel-core/src/config/files/configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from "../caching";
import makeAPI from "../helpers/config-api";
import { makeStaticFileCache } from "./utils";
import pathPatternToRegex from "../pattern-to-regex";
import type { FilePackageData, RelativeConfig, ConfigFile } from "./types";

const debug = buildDebug("babel:config:loading:files:configuration");
Expand Down Expand Up @@ -240,15 +241,24 @@ const readConfigJSON5 = makeStaticFileCache((filepath, content) => {
});

const readIgnoreConfig = makeStaticFileCache((filepath, content) => {
const ignore = content
const ignoreDir = path.dirname(filepath);
const ignorePatterns = content
.split("\n")
.map(line => line.replace(/#(.*?)$/, "").trim())
.filter(line => !!line);

for (const pattern of ignorePatterns) {
if (pattern[0] === "!") {
throw new Error(`Negation of file paths is not supported.`);
}
}

return {
filepath,
dirname: path.dirname(filepath),
ignore,
ignore: ignorePatterns.map(pattern =>
pathPatternToRegex(pattern, ignoreDir),
),
};
});

Expand Down
2 changes: 1 addition & 1 deletion packages/babel-core/src/config/files/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export type ConfigFile = {
export type IgnoreFile = {
filepath: string,
dirname: string,
ignore: Array<string>,
ignore: Array<RegExp>,
};

export type RelativeConfig = {
Expand Down
51 changes: 51 additions & 0 deletions packages/babel-core/src/config/pattern-to-regex.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// @flow
import path from "path";
import escapeRegExp from "lodash/escapeRegExp";

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}?`;

/**
* 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 default function pathToPattern(
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(""),
);
}
10 changes: 5 additions & 5 deletions packages/babel-core/src/config/validation/option-assertions.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,14 +192,14 @@ export function assertBabelrcSearch(

if (Array.isArray(value)) {
value.forEach((item, i) => {
if (typeof item !== "string") {
throw new Error(`.${key}[${i}] must be a string.`);
if (!checkValidTest(value)) {
throw new Error(`.${key}[${i}] must be a string/Function/RegExp.`);
}
});
} else if (typeof value !== "string") {
} else if (!checkValidTest(value)) {
throw new Error(
`.${key} must be a undefined, a boolean, a string, ` +
`or an array of strings, got ${JSON.stringify(value)}`,
`.${key} must be a undefined, a boolean, a string/Function/RegExp ` +
`or an array of those, got ${JSON.stringify(value)}`,
);
}
return (value: any);
Expand Down
2 changes: 1 addition & 1 deletion packages/babel-core/src/config/validation/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ export type OverridesList = Array<ValidatedOptions>;
export type ConfigApplicableTest = IgnoreItem | Array<IgnoreItem>;

export type ConfigFileSearch = string | boolean;
export type BabelrcSearch = boolean | string | Array<string>;
export type BabelrcSearch = boolean | IgnoreItem | IgnoreList;
export type SourceMapsOption = boolean | "inline" | "both";
export type SourceTypeOption = "module" | "script" | "unambiguous";
export type CompactOption = boolean | "auto";
Expand Down
14 changes: 6 additions & 8 deletions packages/babel-core/test/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import Plugin from "../lib/config/plugin";
import generator from "@babel/generator";

function assertIgnored(result) {
expect(result).toBeFalsy();
expect(result).toBeNull();
}

function assertNotIgnored(result) {
expect(result.ignored).toBeFalsy();
expect(result).not.toBeNull();
}

function transform(code, opts) {
Expand All @@ -36,13 +36,11 @@ function transformFileSync(filename, opts) {
});
}

// shim
function transformAsync(code, opts) {
return {
then: function(resolve) {
resolve(transform(code, opts));
},
};
return babel.transformAsync(code, {
cwd: __dirname,
...opts,
});
}

describe("parser and generator options", function() {
Expand Down
1 change: 0 additions & 1 deletion packages/babel-register/src/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ function hookExtensions(exts) {

export function revert() {
if (piratesRevert) piratesRevert();
delete require.cache[require.resolve(__filename)];
}

register();
Expand Down
Loading

0 comments on commit 52a5690

Please sign in to comment.