232 lines
7.8 KiB
JavaScript
232 lines
7.8 KiB
JavaScript
|
/**
|
||
|
* @fileoverview `IgnorePattern` class.
|
||
|
*
|
||
|
* `IgnorePattern` class has the set of glob patterns and the base path.
|
||
|
*
|
||
|
* It provides two static methods.
|
||
|
*
|
||
|
* - `IgnorePattern.createDefaultIgnore(cwd)`
|
||
|
* Create the default predicate function.
|
||
|
* - `IgnorePattern.createIgnore(ignorePatterns)`
|
||
|
* Create the predicate function from multiple `IgnorePattern` objects.
|
||
|
*
|
||
|
* It provides two properties and a method.
|
||
|
*
|
||
|
* - `patterns`
|
||
|
* The glob patterns that ignore to lint.
|
||
|
* - `basePath`
|
||
|
* The base path of the glob patterns. If absolute paths existed in the
|
||
|
* glob patterns, those are handled as relative paths to the base path.
|
||
|
* - `getPatternsRelativeTo(basePath)`
|
||
|
* Get `patterns` as modified for a given base path. It modifies the
|
||
|
* absolute paths in the patterns as prepending the difference of two base
|
||
|
* paths.
|
||
|
*
|
||
|
* `ConfigArrayFactory` creates `IgnorePattern` objects when it processes
|
||
|
* `ignorePatterns` properties.
|
||
|
*
|
||
|
* @author Toru Nagashima <https://github.com/mysticatea>
|
||
|
*/
|
||
|
"use strict";
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// Requirements
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
const assert = require("assert");
|
||
|
const path = require("path");
|
||
|
const ignore = require("ignore");
|
||
|
const debug = require("debug")("eslint:ignore-pattern");
|
||
|
|
||
|
/** @typedef {ReturnType<import("ignore").default>} Ignore */
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// Helpers
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Get the path to the common ancestor directory of given paths.
|
||
|
* @param {string[]} sourcePaths The paths to calculate the common ancestor.
|
||
|
* @returns {string} The path to the common ancestor directory.
|
||
|
*/
|
||
|
function getCommonAncestorPath(sourcePaths) {
|
||
|
let result = sourcePaths[0];
|
||
|
|
||
|
for (let i = 1; i < sourcePaths.length; ++i) {
|
||
|
const a = result;
|
||
|
const b = sourcePaths[i];
|
||
|
|
||
|
// Set the shorter one (it's the common ancestor if one includes the other).
|
||
|
result = a.length < b.length ? a : b;
|
||
|
|
||
|
// Set the common ancestor.
|
||
|
for (let j = 0, lastSepPos = 0; j < a.length && j < b.length; ++j) {
|
||
|
if (a[j] !== b[j]) {
|
||
|
result = a.slice(0, lastSepPos);
|
||
|
break;
|
||
|
}
|
||
|
if (a[j] === path.sep) {
|
||
|
lastSepPos = j;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return result || path.sep;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Make relative path.
|
||
|
* @param {string} from The source path to get relative path.
|
||
|
* @param {string} to The destination path to get relative path.
|
||
|
* @returns {string} The relative path.
|
||
|
*/
|
||
|
function relative(from, to) {
|
||
|
const relPath = path.relative(from, to);
|
||
|
|
||
|
if (path.sep === "/") {
|
||
|
return relPath;
|
||
|
}
|
||
|
return relPath.split(path.sep).join("/");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the trailing slash if existed.
|
||
|
* @param {string} filePath The path to check.
|
||
|
* @returns {string} The trailing slash if existed.
|
||
|
*/
|
||
|
function dirSuffix(filePath) {
|
||
|
const isDir = (
|
||
|
filePath.endsWith(path.sep) ||
|
||
|
(process.platform === "win32" && filePath.endsWith("/"))
|
||
|
);
|
||
|
|
||
|
return isDir ? "/" : "";
|
||
|
}
|
||
|
|
||
|
const DefaultPatterns = Object.freeze(["/node_modules/*", "/bower_components/*"]);
|
||
|
const DotPatterns = Object.freeze([".*", "!../"]);
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// Public
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
class IgnorePattern {
|
||
|
|
||
|
/**
|
||
|
* The default patterns.
|
||
|
* @type {string[]}
|
||
|
*/
|
||
|
static get DefaultPatterns() {
|
||
|
return DefaultPatterns;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Create the default predicate function.
|
||
|
* @param {string} cwd The current working directory.
|
||
|
* @returns {((filePath:string, dot:boolean) => boolean) & {basePath:string; patterns:string[]}}
|
||
|
* The preficate function.
|
||
|
* The first argument is an absolute path that is checked.
|
||
|
* The second argument is the flag to not ignore dotfiles.
|
||
|
* If the predicate function returned `true`, it means the path should be ignored.
|
||
|
*/
|
||
|
static createDefaultIgnore(cwd) {
|
||
|
return this.createIgnore([new IgnorePattern(DefaultPatterns, cwd)]);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Create the predicate function from multiple `IgnorePattern` objects.
|
||
|
* @param {IgnorePattern[]} ignorePatterns The list of ignore patterns.
|
||
|
* @returns {((filePath:string, dot?:boolean) => boolean) & {basePath:string; patterns:string[]}}
|
||
|
* The preficate function.
|
||
|
* The first argument is an absolute path that is checked.
|
||
|
* The second argument is the flag to not ignore dotfiles.
|
||
|
* If the predicate function returned `true`, it means the path should be ignored.
|
||
|
*/
|
||
|
static createIgnore(ignorePatterns) {
|
||
|
debug("Create with: %o", ignorePatterns);
|
||
|
|
||
|
const basePath = getCommonAncestorPath(ignorePatterns.map(p => p.basePath));
|
||
|
const patterns = [].concat(
|
||
|
...ignorePatterns.map(p => p.getPatternsRelativeTo(basePath))
|
||
|
);
|
||
|
const ig = ignore().add([...DotPatterns, ...patterns]);
|
||
|
const dotIg = ignore().add(patterns);
|
||
|
|
||
|
debug(" processed: %o", { basePath, patterns });
|
||
|
|
||
|
return Object.assign(
|
||
|
(filePath, dot = false) => {
|
||
|
assert(path.isAbsolute(filePath), "'filePath' should be an absolute path.");
|
||
|
const relPathRaw = relative(basePath, filePath);
|
||
|
const relPath = relPathRaw && (relPathRaw + dirSuffix(filePath));
|
||
|
const adoptedIg = dot ? dotIg : ig;
|
||
|
const result = relPath !== "" && adoptedIg.ignores(relPath);
|
||
|
|
||
|
debug("Check", { filePath, dot, relativePath: relPath, result });
|
||
|
return result;
|
||
|
},
|
||
|
{ basePath, patterns }
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Initialize a new `IgnorePattern` instance.
|
||
|
* @param {string[]} patterns The glob patterns that ignore to lint.
|
||
|
* @param {string} basePath The base path of `patterns`.
|
||
|
*/
|
||
|
constructor(patterns, basePath) {
|
||
|
assert(path.isAbsolute(basePath), "'basePath' should be an absolute path.");
|
||
|
|
||
|
/**
|
||
|
* The glob patterns that ignore to lint.
|
||
|
* @type {string[]}
|
||
|
*/
|
||
|
this.patterns = patterns;
|
||
|
|
||
|
/**
|
||
|
* The base path of `patterns`.
|
||
|
* @type {string}
|
||
|
*/
|
||
|
this.basePath = basePath;
|
||
|
|
||
|
/**
|
||
|
* If `true` then patterns which don't start with `/` will match the paths to the outside of `basePath`. Defaults to `false`.
|
||
|
*
|
||
|
* It's set `true` for `.eslintignore`, `package.json`, and `--ignore-path` for backward compatibility.
|
||
|
* It's `false` as-is for `ignorePatterns` property in config files.
|
||
|
* @type {boolean}
|
||
|
*/
|
||
|
this.loose = false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get `patterns` as modified for a given base path. It modifies the
|
||
|
* absolute paths in the patterns as prepending the difference of two base
|
||
|
* paths.
|
||
|
* @param {string} newBasePath The base path.
|
||
|
* @returns {string[]} Modifired patterns.
|
||
|
*/
|
||
|
getPatternsRelativeTo(newBasePath) {
|
||
|
assert(path.isAbsolute(newBasePath), "'newBasePath' should be an absolute path.");
|
||
|
const { basePath, loose, patterns } = this;
|
||
|
|
||
|
if (newBasePath === basePath) {
|
||
|
return patterns;
|
||
|
}
|
||
|
const prefix = `/${relative(newBasePath, basePath)}`;
|
||
|
|
||
|
return patterns.map(pattern => {
|
||
|
const negative = pattern.startsWith("!");
|
||
|
const head = negative ? "!" : "";
|
||
|
const body = negative ? pattern.slice(1) : pattern;
|
||
|
|
||
|
if (body.startsWith("/") || body.startsWith("../")) {
|
||
|
return `${head}${prefix}${body}`;
|
||
|
}
|
||
|
return loose ? pattern : `${head}${prefix}/**/${body}`;
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
module.exports = { IgnorePattern };
|