"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Pattern = void 0; const os = __importStar(require("os")); const path = __importStar(require("path")); const pathHelper = __importStar(require("./internal-path-helper")); const assert_1 = __importDefault(require("assert")); const minimatch_1 = require("minimatch"); const internal_match_kind_1 = require("./internal-match-kind"); const internal_path_1 = require("./internal-path"); const IS_WINDOWS = process.platform === 'win32'; class Pattern { constructor(patternOrNegate, isImplicitPattern = false, segments, homedir) { /** * Indicates whether matches should be excluded from the result set */ this.negate = false; // Pattern overload let pattern; if (typeof patternOrNegate === 'string') { pattern = patternOrNegate.trim(); } // Segments overload else { // Convert to pattern segments = segments || []; assert_1.default(segments.length, `Parameter 'segments' must not empty`); const root = Pattern.getLiteral(segments[0]); assert_1.default(root && pathHelper.hasAbsoluteRoot(root), `Parameter 'segments' first element must be a root path`); pattern = new internal_path_1.Path(segments).toString().trim(); if (patternOrNegate) { pattern = `!${pattern}`; } } // Negate while (pattern.startsWith('!')) { this.negate = !this.negate; pattern = pattern.substr(1).trim(); } // Normalize slashes and ensures absolute root pattern = Pattern.fixupPattern(pattern, homedir); // Segments this.segments = new internal_path_1.Path(pattern).segments; // Trailing slash indicates the pattern should only match directories, not regular files this.trailingSeparator = pathHelper .normalizeSeparators(pattern) .endsWith(path.sep); pattern = pathHelper.safeTrimTrailingSeparator(pattern); // Search path (literal path prior to the first glob segment) let foundGlob = false; const searchSegments = this.segments .map(x => Pattern.getLiteral(x)) .filter(x => !foundGlob && !(foundGlob = x === '')); this.searchPath = new internal_path_1.Path(searchSegments).toString(); // Root RegExp (required when determining partial match) this.rootRegExp = new RegExp(Pattern.regExpEscape(searchSegments[0]), IS_WINDOWS ? 'i' : ''); this.isImplicitPattern = isImplicitPattern; // Create minimatch const minimatchOptions = { dot: true, nobrace: true, nocase: IS_WINDOWS, nocomment: true, noext: true, nonegate: true }; pattern = IS_WINDOWS ? pattern.replace(/\\/g, '/') : pattern; this.minimatch = new minimatch_1.Minimatch(pattern, minimatchOptions); } /** * Matches the pattern against the specified path */ match(itemPath) { // Last segment is globstar? if (this.segments[this.segments.length - 1] === '**') { // Normalize slashes itemPath = pathHelper.normalizeSeparators(itemPath); // Append a trailing slash. Otherwise Minimatch will not match the directory immediately // preceding the globstar. For example, given the pattern `/foo/**`, Minimatch returns // false for `/foo` but returns true for `/foo/`. Append a trailing slash to handle that quirk. if (!itemPath.endsWith(path.sep) && this.isImplicitPattern === false) { // Note, this is safe because the constructor ensures the pattern has an absolute root. // For example, formats like C: and C:foo on Windows are resolved to an absolute root. itemPath = `${itemPath}${path.sep}`; } } else { // Normalize slashes and trim unnecessary trailing slash itemPath = pathHelper.safeTrimTrailingSeparator(itemPath); } // Match if (this.minimatch.match(itemPath)) { return this.trailingSeparator ? internal_match_kind_1.MatchKind.Directory : internal_match_kind_1.MatchKind.All; } return internal_match_kind_1.MatchKind.None; } /** * Indicates whether the pattern may match descendants of the specified path */ partialMatch(itemPath) { // Normalize slashes and trim unnecessary trailing slash itemPath = pathHelper.safeTrimTrailingSeparator(itemPath); // matchOne does not handle root path correctly if (pathHelper.dirname(itemPath) === itemPath) { return this.rootRegExp.test(itemPath); } return this.minimatch.matchOne(itemPath.split(IS_WINDOWS ? /\\+/ : /\/+/), this.minimatch.set[0], true); } /** * Escapes glob patterns within a path */ static globEscape(s) { return (IS_WINDOWS ? s : s.replace(/\\/g, '\\\\')) // escape '\' on Linux/macOS .replace(/(\[)(?=[^/]+\])/g, '[[]') // escape '[' when ']' follows within the path segment .replace(/\?/g, '[?]') // escape '?' .replace(/\*/g, '[*]'); // escape '*' } /** * Normalizes slashes and ensures absolute root */ static fixupPattern(pattern, homedir) { // Empty assert_1.default(pattern, 'pattern cannot be empty'); // Must not contain `.` segment, unless first segment // Must not contain `..` segment const literalSegments = new internal_path_1.Path(pattern).segments.map(x => Pattern.getLiteral(x)); assert_1.default(literalSegments.every((x, i) => (x !== '.' || i === 0) && x !== '..'), `Invalid pattern '${pattern}'. Relative pathing '.' and '..' is not allowed.`); // Must not contain globs in root, e.g. Windows UNC path \\foo\b*r assert_1.default(!pathHelper.hasRoot(pattern) || literalSegments[0], `Invalid pattern '${pattern}'. Root segment must not contain globs.`); // Normalize slashes pattern = pathHelper.normalizeSeparators(pattern); // Replace leading `.` segment if (pattern === '.' || pattern.startsWith(`.${path.sep}`)) { pattern = Pattern.globEscape(process.cwd()) + pattern.substr(1); } // Replace leading `~` segment else if (pattern === '~' || pattern.startsWith(`~${path.sep}`)) { homedir = homedir || os.homedir(); assert_1.default(homedir, 'Unable to determine HOME directory'); assert_1.default(pathHelper.hasAbsoluteRoot(homedir), `Expected HOME directory to be a rooted path. Actual '${homedir}'`); pattern = Pattern.globEscape(homedir) + pattern.substr(1); } // Replace relative drive root, e.g. pattern is C: or C:foo else if (IS_WINDOWS && (pattern.match(/^[A-Z]:$/i) || pattern.match(/^[A-Z]:[^\\]/i))) { let root = pathHelper.ensureAbsoluteRoot('C:\\dummy-root', pattern.substr(0, 2)); if (pattern.length > 2 && !root.endsWith('\\')) { root += '\\'; } pattern = Pattern.globEscape(root) + pattern.substr(2); } // Replace relative root, e.g. pattern is \ or \foo else if (IS_WINDOWS && (pattern === '\\' || pattern.match(/^\\[^\\]/))) { let root = pathHelper.ensureAbsoluteRoot('C:\\dummy-root', '\\'); if (!root.endsWith('\\')) { root += '\\'; } pattern = Pattern.globEscape(root) + pattern.substr(1); } // Otherwise ensure absolute root else { pattern = pathHelper.ensureAbsoluteRoot(Pattern.globEscape(process.cwd()), pattern); } return pathHelper.normalizeSeparators(pattern); } /** * Attempts to unescape a pattern segment to create a literal path segment. * Otherwise returns empty string. */ static getLiteral(segment) { let literal = ''; for (let i = 0; i < segment.length; i++) { const c = segment[i]; // Escape if (c === '\\' && !IS_WINDOWS && i + 1 < segment.length) { literal += segment[++i]; continue; } // Wildcard else if (c === '*' || c === '?') { return ''; } // Character set else if (c === '[' && i + 1 < segment.length) { let set = ''; let closed = -1; for (let i2 = i + 1; i2 < segment.length; i2++) { const c2 = segment[i2]; // Escape if (c2 === '\\' && !IS_WINDOWS && i2 + 1 < segment.length) { set += segment[++i2]; continue; } // Closed else if (c2 === ']') { closed = i2; break; } // Otherwise else { set += c2; } } // Closed? if (closed >= 0) { // Cannot convert if (set.length > 1) { return ''; } // Convert to literal if (set) { literal += set; i = closed; continue; } } // Otherwise fall thru } // Append literal += c; } return literal; } /** * Escapes regexp special characters * https://javascript.info/regexp-escaping */ static regExpEscape(s) { return s.replace(/[[\\^$.|?*+()]/g, '\\$&'); } } exports.Pattern = Pattern; //# sourceMappingURL=internal-pattern.js.map