Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Treat string ignore/only/test/include/exclude values as paths with only basic pattern matching #8327

Merged
merged 3 commits into from Jul 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/babel-core/package.json
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
@@ -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
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
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
@@ -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
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
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
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
Expand Up @@ -101,7 +101,6 @@ function hookExtensions(exts) {

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

register();
Expand Down