@castlenine/vite-remove-attribute 1.0.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/sourcemap.ts","../src/utilities.ts","../src/index.ts"],"sourcesContent":["import type { Range } from './utilities';\n\n/**\n * Source map v3 object, as accepted by Vite's `transform` hook (`map`)\n */\ninterface SourceMap {\n\tversion: 3;\n\tsources: string[];\n\tsourcesContent: string[];\n\tnames: string[];\n\tmappings: string;\n}\n\nconst BASE64_CHARACTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\nconst WORD_CHARACTER_REGEX = /\\w/;\n\n/**\n * Encodes a single integer value as a base64 VLQ (Variable Length Quantity) string.\n *\n * @remarks\n * Base64 VLQ is the encoding format used for numbers in source map `mappings` fields.\n * This function encodes both positive and negative integers using bitwise operations,\n * setting the least significant bit for sign and using continuation bits for multi-digit numbers.\n *\n * @param value - The integer to encode. Can be positive, negative, or zero.\n *\n * @returns The VLQ Base64-encoded string representation.\n *\n * @see [Source Map V3 Spec](https://sourcemaps.info/spec.html)\n */\nfunction encodeVlq(value: number): string {\n\tlet vlq = value < 0 ? (-value << 1) | 1 : value << 1;\n\tlet output = '';\n\n\tdo {\n\t\tlet digit = vlq & 31;\n\n\t\tvlq >>>= 5;\n\n\t\tif (vlq > 0) {\n\t\t\tdigit |= 32;\n\t\t}\n\n\t\toutput += BASE64_CHARACTERS.charAt(digit);\n\t} while (vlq > 0);\n\n\treturn output;\n}\n\n/**\n * Builds the `mappings` string one generated line at a time, encoding every segment as deltas from the previous one\n */\nclass MappingsWriter {\n\tprivate readonly lines: string[] = [];\n\tprivate segments: string[] = [];\n\tprivate lastGeneratedColumn = 0;\n\tprivate lastOriginalLine = 0;\n\tprivate lastOriginalColumn = 0;\n\tprivate lastMappedColumn = -1;\n\n\taddSegment(generatedColumn: number, originalLine: number, originalColumn: number): void {\n\t\t// Two candidates for the same generated column always describe the same original position\n\t\tif (generatedColumn === this.lastMappedColumn) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.segments.push(\n\t\t\tencodeVlq(generatedColumn - this.lastGeneratedColumn) +\n\t\t\t\tencodeVlq(0) +\n\t\t\t\tencodeVlq(originalLine - this.lastOriginalLine) +\n\t\t\t\tencodeVlq(originalColumn - this.lastOriginalColumn),\n\t\t);\n\t\tthis.lastGeneratedColumn = generatedColumn;\n\t\tthis.lastOriginalLine = originalLine;\n\t\tthis.lastOriginalColumn = originalColumn;\n\t\tthis.lastMappedColumn = generatedColumn;\n\t}\n\n\tendLine(): void {\n\t\tthis.lines.push(this.segments.join(','));\n\t\tthis.segments = [];\n\t\tthis.lastGeneratedColumn = 0;\n\t\tthis.lastMappedColumn = -1;\n\t}\n\n\ttoString(): string {\n\t\tthis.endLine();\n\n\t\treturn this.lines.join(';');\n\t}\n}\n\n/**\n * Generates a source map for a given `source` string with the specified ranges removed.\n *\n * Emits a segment at the start of each generated line, at each position where the original text was cut, and at\n * every word boundary in between, allowing consumers to resolve any token to its original line and column.\n *\n * @param source - The original source code string.\n * @param ranges - Sorted, non-overlapping `[start, end)` character ranges to be removed.\n * @param file - The original file name or path. Used as the single source entry in the resulting source map.\n *\n * @returns The generated SourceMap object with correct mappings after range removal.\n */\nfunction generateRemovalSourceMap(source: string, ranges: Range[], file: string): SourceMap {\n\tconst WRITER = new MappingsWriter();\n\n\tlet generatedColumn = 0;\n\tlet originalLine = 0;\n\tlet originalColumn = 0;\n\tlet previousCharacter = '';\n\tlet cursor = 0;\n\n\t/**\n\t * Advances the original position from `from` to `to`, updating `originalLine` and `originalColumn`\n\t * to correctly reflect skipping a removed range.\n\t *\n\t * @param from - Start index in the original source.\n\t * @param to - End index in the original source.\n\t */\n\tconst ADVANCE_ORIGINAL = (from: number, to: number): void => {\n\t\tfor (let index = from; index < to; index++) {\n\t\t\tif (source.charAt(index) === '\\n') {\n\t\t\t\toriginalLine++;\n\t\t\t\toriginalColumn = 0;\n\t\t\t} else {\n\t\t\t\toriginalColumn++;\n\t\t\t}\n\t\t}\n\t};\n\n\t/**\n\t * Emits source map segments for the kept region from `from` to `to`.\n\t *\n\t * @remarks\n\t * A segment is written at every new line and at every word boundary change (as determined by\n\t * `WORD_CHARACTER_REGEX`), so tools consuming the source map can resolve the original location of any token in\n\t * the generated content. The `generatedColumn`, `originalLine` and `originalColumn` counters are advanced as the\n\t * region is traversed, and `needsSegment` forces a segment right after a cut.\n\t *\n\t * @param from - Start index of the kept region.\n\t * @param to - End index of the kept region.\n\t */\n\tconst WRITE_KEPT = (from: number, to: number): void => {\n\t\tlet needsSegment = from < to;\n\n\t\tfor (let index = from; index < to; index++) {\n\t\t\tconst CHARACTER = source.charAt(index);\n\n\t\t\tif (CHARACTER === '\\n') {\n\t\t\t\tWRITER.endLine();\n\t\t\t\tgeneratedColumn = 0;\n\t\t\t\toriginalLine++;\n\t\t\t\toriginalColumn = 0;\n\t\t\t\tneedsSegment = true;\n\t\t\t} else {\n\t\t\t\tif (needsSegment || WORD_CHARACTER_REGEX.test(CHARACTER) !== WORD_CHARACTER_REGEX.test(previousCharacter)) {\n\t\t\t\t\tWRITER.addSegment(generatedColumn, originalLine, originalColumn);\n\t\t\t\t\tneedsSegment = false;\n\t\t\t\t}\n\n\t\t\t\tgeneratedColumn++;\n\t\t\t\toriginalColumn++;\n\t\t\t}\n\n\t\t\tpreviousCharacter = CHARACTER;\n\t\t}\n\t};\n\n\t// Emit the kept text before each removed range, then advance the original position through the removed\n\t// content; whatever follows the last range is emitted afterwards\n\tfor (const [START, END] of ranges) {\n\t\tWRITE_KEPT(cursor, START);\n\t\tADVANCE_ORIGINAL(START, END);\n\t\tcursor = END;\n\t}\n\n\tWRITE_KEPT(cursor, source.length);\n\n\treturn {\n\t\tversion: 3,\n\t\tsources: [file],\n\t\tsourcesContent: [source],\n\t\tnames: [],\n\t\tmappings: WRITER.toString(),\n\t};\n}\n\nexport type { SourceMap };\nexport { encodeVlq, generateRemovalSourceMap };\n","import type { Options, ResolvedOptions } from './types';\n\nimport { relative, sep } from 'node:path';\n\nconst DEFAULT_IGNORE_PATHS: string[] = [\n\t// Node modules\n\t'node_modules',\n\n\t// Git\n\t'.git',\n\n\t// IDE configurations\n\t'.idea', // JetBrains IDEs (e.g., WebStorm)\n\t'.vscode', // Visual Studio Code\n\n\t// OS generated files\n\t'.DS_Store', // macOS\n\t'Thumbs.db', // Windows\n\n\t// Environment variables\n\t'.env',\n\t'.env.*', // .env.development, .env.production, etc.\n\n\t// Logs\n\t'logs',\n\t'*.log',\n\n\t// Svelte\n\t'public', // Svelte.js public folder\n\t'build', // Svelte.js build folder\n\n\t// SvelteKit\n\t'.svelte-kit', // SvelteKit generates this folder\n\n\t// Dist\n\t'dist', // Distribution folder\n\n\t// Vue.js\n\t'.nuxt', // Nuxt.js generates this folder\n\n\t// React.js\n\t'.next', // Next.js generates this folder\n\n\t// Remix.js\n\t'.remix', // Remix.js cache\n\n\t// Angular\n\t'e2e', // End-to-end tests in Angular\n\t'angular.json', // Angular CLI configuration\n\t'browserslist', // Browser compatibility list for Angular\n\n\t'.cache', // Cache files for various tools\n];\n\nconst REGEX_SPECIAL_CHARACTERS_REGEX = /[.*+?^${}()|[\\]\\\\]/g;\nconst LEADING_RELATIVE_PREFIX_REGEX = /^(?:\\.\\/|\\/)+/;\nconst TRAILING_SLASHES_REGEX = /\\/+$/;\nconst LEADING_PARENT_SEGMENTS_REGEX = /^(?:\\.\\.\\/)+/;\nconst LEADING_DOTS_REGEX = /^\\.+/;\n\nconst TOKEN_REGEX_CACHE = new Map<string, RegExp>();\nconst EXTENSION_REGEX_CACHE = new Map<string, RegExp>();\n\nfunction escapeRegExp(value: string): string {\n\treturn value.replace(REGEX_SPECIAL_CHARACTERS_REGEX, '\\\\$&');\n}\n\nfunction getOptions(options: Options): ResolvedOptions {\n\treturn {\n\t\textensions: Array.isArray(options.extensions) ? options.extensions : [],\n\t\tattributes: Array.isArray(options.attributes) ? options.attributes : [],\n\t\tignoreFolders: Array.isArray(options.ignoreFolders) ? options.ignoreFolders : [],\n\t\tignoreFiles: Array.isArray(options.ignoreFiles) ? options.ignoreFiles : [],\n\t\tignoreDefaults: options.ignoreDefaults !== false,\n\t};\n}\n\n/**\n * Normalizes a user-supplied ignore token by trimming whitespace,\n * removing leading './' or '/', and stripping trailing '/' characters.\n *\n * @param path - The ignore token to normalize.\n *\n * @returns The normalized ignore token.\n */\nfunction cleanIgnoredPath(path: string): string {\n\treturn path.trim().replace(LEADING_RELATIVE_PREFIX_REGEX, '').replace(TRAILING_SLASHES_REGEX, '');\n}\n\n/**\n * Normalizes and de-duplicates ignore tokens by:\n * - Trimming whitespace from each path\n * - Removing leading './' or '/', and trailing '/' characters\n * - Dropping empty and root-only entries (such as `''`, `.`, `/`, and `./`)\n * - Removing duplicate entries in the result\n *\n * @param paths - An array of ignore path tokens to be cleaned and de-duplicated.\n *\n * @returns A new array containing unique, cleaned ignore tokens, with all empty or root-only entries omitted.\n */\nfunction cleanIgnoredPaths(paths: string[]): string[] {\n\tconst CLEANED = paths\n\t\t.filter((path): path is string => typeof path === 'string')\n\t\t.map(cleanIgnoredPath)\n\t\t.filter((path) => path !== '' && path !== '.');\n\n\treturn [...new Set(CLEANED)];\n}\n\n/**\n * Returns the combined and cleaned list of ignored paths based on the provided {@link ResolvedOptions}.\n *\n * @remarks\n * - Merges `ignoreFolders` and `ignoreFiles` into a single list.\n * - Cleans each entry using {@link cleanIgnoredPaths}.\n * - If `ignoreDefaults` is not explicitly set to `false`, the list also includes {@link DEFAULT_IGNORE_PATHS}.\n * - The resulting list is de-duplicated.\n *\n * @param options - The resolved options containing `ignoreFolders`, `ignoreFiles` and the `ignoreDefaults` flag.\n *\n * @returns An array of unique, cleaned ignore path tokens, including the built-in defaults unless\n * {@link ResolvedOptions.ignoreDefaults} is `false`.\n */\nfunction getIgnoredPaths(options: ResolvedOptions): string[] {\n\tconst CONFIGURED = cleanIgnoredPaths([...options.ignoreFolders, ...options.ignoreFiles]);\n\n\treturn options.ignoreDefaults ? [...new Set([...CONFIGURED, ...DEFAULT_IGNORE_PATHS])] : CONFIGURED;\n}\n\n/**\n * Removes the `?query` suffix that Vite appends to module IDs.\n *\n * For example, given `/src/App.svelte?svelte&type=style&lang.css`, this function will return `/src/App.svelte`.\n *\n * @param id - The module ID which may include a `?query` suffix.\n *\n * @returns The module ID without any `?query` part.\n */\nfunction stripQuery(id: string): string {\n\tconst QUERY_INDEX = id.indexOf('?');\n\n\treturn QUERY_INDEX === -1 ? id : id.slice(0, QUERY_INDEX);\n}\n\n/**\n * Returns the path of a module ID relative to the Vite root directory, using POSIX separators.\n *\n * @param id - The module ID to be converted to a relative path.\n * @param root - The root directory to which the path will be made relative.\n *\n * @returns The relative path from the root to the module ID, using '/' as the separator.\n */\nfunction toRelativePath(id: string, root: string): string {\n\treturn relative(root, stripQuery(id)).split(sep).join('/');\n}\n\n/**\n * Returns a cached or newly-created regular expression to match a path segment token,\n * supporting `*` wildcards matching within a segment (but not across path separators).\n *\n * The regular expression is built so that `*` matches any sequence of characters except '/'.\n * The regex is cached for subsequent calls with the same token.\n *\n * @param token - The ignore token, possibly containing `*` wildcards.\n *\n * @returns The regular expression corresponding to the token, matching segment boundaries.\n */\nfunction getTokenRegex(token: string): RegExp {\n\tconst CACHED = TOKEN_REGEX_CACHE.get(token);\n\n\tif (CACHED) {\n\t\treturn CACHED;\n\t}\n\n\t// `*` matches within a single segment; everything else is literal\n\tconst PATTERN = token.split('*').map(escapeRegExp).join('[^/]*');\n\t// eslint-disable-next-line security/detect-non-literal-regexp -- the token is regex-escaped above\n\tconst REGEX = new RegExp(`(?:^|/)${PATTERN}(?:/|$)`);\n\n\tTOKEN_REGEX_CACHE.set(token, REGEX);\n\n\treturn REGEX;\n}\n\n/**\n * Checks if the given relative path matches any of the provided ignore tokens,\n * comparing on path-segment boundaries. Ignores leading parent directory segments.\n *\n * @remarks\n * This function ignores absolute IDs to avoid false positives from folder names that coincidentally\n * contain ignore tokens. For example, in environments like Cloudflare where a repo may be cloned\n * to a directory such as `/opt/buildhome/repo`, a token like `build` should not match simply\n * because it's part of the parent path. Only the path relative to the Vite root is considered.\n *\n * Leading `../` segments are stripped from the path so that modules resolved outside the root\n * (e.g. `../../.pnpm/x/node_modules/y/index.js`) still have the opportunity to match ignore tokens\n * like `node_modules` against their segments.\n *\n * @param relativePath - The path of the module relative to the Vite root.\n * @param tokens - The list of ignore tokens, possibly including `*` wildcards.\n *\n * @returns `true` if the relative path matches any ignore token; otherwise, `false`.\n */\nfunction hasIgnorePath(relativePath: string, tokens: string[]): boolean {\n\tconst PATH = relativePath.replace(LEADING_PARENT_SEGMENTS_REGEX, '');\n\n\treturn tokens.some((token) => getTokenRegex(token).test(PATH));\n}\n\n/**\n * Returns a regular expression that matches any of the provided file extensions at the end of a string.\n *\n * The extensions are matched case-insensitively and can be provided with or without leading dots.\n * The resulting regex is cached for subsequent calls with the same set of extensions.\n *\n * @param extensions - An array of file extension strings (with or without leading dots).\n *\n * @returns A RegExp instance matching any of the specified extensions as a file suffix.\n */\nfunction getExtensionRegex(extensions: string[]): RegExp {\n\tconst KEY = extensions.join('|');\n\tconst CACHED = EXTENSION_REGEX_CACHE.get(KEY);\n\n\tif (CACHED) {\n\t\treturn CACHED;\n\t}\n\n\tconst PATTERN = extensions.map((extension) => escapeRegExp(extension.replace(LEADING_DOTS_REGEX, ''))).join('|');\n\t// eslint-disable-next-line security/detect-non-literal-regexp -- the extensions are regex-escaped above\n\tconst REGEX = new RegExp(`\\\\.(?:${PATTERN})$`, 'i');\n\n\tEXTENSION_REGEX_CACHE.set(KEY, REGEX);\n\n\treturn REGEX;\n}\n\n/**\n * Determines whether the provided module id (with query suffix stripped) ends with one of the specified extensions.\n *\n * @param id - The module identifier, possibly including a query suffix (e.g., `file.js?raw`).\n * @param extensions - An array of file extension strings (with or without leading dots).\n *\n * @returns `true` if the module id (excluding the query suffix) ends with one of the provided extensions;\n * otherwise, `false`.\n */\nfunction hasExtension(id: string, extensions: string[]): boolean {\n\tif (extensions.length === 0) {\n\t\treturn false;\n\t}\n\n\treturn getExtensionRegex(extensions).test(stripQuery(id));\n}\n\ninterface ExpressionFrame {\n\tkind: 'expression';\n\tdepth: number;\n}\n\ninterface TemplateFrame {\n\tkind: 'template';\n}\n\ntype Frame = ExpressionFrame | TemplateFrame;\n\ntype ScanStep = 'closed' | 'continue' | 'skip-next';\n\n/**\n * Character-by-character scanner for an `={…}` expression: tracks nested braces, quoted strings, template literals\n * and their `${…}` placeholders, and reports when the opening brace is closed\n */\nclass ExpressionScanner {\n\tprivate readonly frames: Frame[] = [{ kind: 'expression', depth: 1 }];\n\tprivate stringQuote = '';\n\tprivate isEscaped = false;\n\n\tstep(character: string, next: string): ScanStep {\n\t\tif (this.isEscaped) {\n\t\t\tthis.isEscaped = false;\n\n\t\t\treturn 'continue';\n\t\t}\n\n\t\tif (character === '\\\\') {\n\t\t\tthis.isEscaped = true;\n\n\t\t\treturn 'continue';\n\t\t}\n\n\t\tif (this.stringQuote !== '') {\n\t\t\tif (character === this.stringQuote) {\n\t\t\t\tthis.stringQuote = '';\n\t\t\t}\n\n\t\t\treturn 'continue';\n\t\t}\n\n\t\tconst FRAME = this.frames.at(-1);\n\n\t\tif (!FRAME) {\n\t\t\treturn 'continue';\n\t\t}\n\n\t\treturn FRAME.kind === 'template' ? this.stepTemplate(character, next) : this.stepExpression(FRAME, character);\n\t}\n\n\tprivate stepTemplate(character: string, next: string): ScanStep {\n\t\tif (character === '`') {\n\t\t\tthis.frames.pop();\n\t\t} else if (character === '$' && next === '{') {\n\t\t\tthis.frames.push({ kind: 'expression', depth: 1 });\n\n\t\t\treturn 'skip-next';\n\t\t}\n\n\t\treturn 'continue';\n\t}\n\n\tprivate stepExpression(frame: ExpressionFrame, character: string): ScanStep {\n\t\tif (character === \"'\" || character === '\"') {\n\t\t\tthis.stringQuote = character;\n\t\t} else if (character === '`') {\n\t\t\tthis.frames.push({ kind: 'template' });\n\t\t} else if (character === '{') {\n\t\t\tframe.depth++;\n\t\t} else if (character === '}') {\n\t\t\treturn this.closeBrace(frame);\n\t\t}\n\n\t\treturn 'continue';\n\t}\n\n\tprivate closeBrace(frame: ExpressionFrame): ScanStep {\n\t\tframe.depth--;\n\n\t\tif (frame.depth > 0) {\n\t\t\treturn 'continue';\n\t\t}\n\n\t\tif (this.frames.length === 1) {\n\t\t\treturn 'closed';\n\t\t}\n\n\t\tthis.frames.pop();\n\n\t\treturn 'continue';\n\t}\n}\n\n/**\n * Finds the closing brace for an `={…}` attribute value in a string of markup. Returns the index of the closing\n * brace, or `-1` if the braces are unbalanced or not found.\n *\n * @remarks\n * A regular expression cannot do this: the expression may hold a template literal whose `${…}` placeholders nest\n * further braces, and a brace inside a string is text rather than structure. The scanner tracks both.\n *\n * @param input - The input string containing the markup or code.\n * @param openingBraceIndex - The index of the opening `{` character to start scanning from.\n *\n * @returns The index of the corresponding closing `}` brace, or `-1` if unmatched.\n *\n * @example\n * ```ts\n * findExpressionEnd('foo={a + {b: `1${two}`}}', 4); // returns the index of the matching }\n * ```\n */\nfunction findExpressionEnd(input: string, openingBraceIndex: number): number {\n\tconst SCANNER = new ExpressionScanner();\n\n\tfor (let index = openingBraceIndex + 1; index < input.length; index++) {\n\t\tconst STEP = SCANNER.step(input.charAt(index), input.charAt(index + 1));\n\n\t\tif (STEP === 'closed') {\n\t\t\treturn index;\n\t\t}\n\n\t\tif (STEP === 'skip-next') {\n\t\t\tindex++;\n\t\t}\n\t}\n\n\treturn -1;\n}\n\n/**\n * Half-open `[start, end)` range of characters in the original input\n */\ntype Range = readonly [start: number, end: number];\n\n/**\n * Finds all ranges (start and end indices) for a specific attribute in the given markup input.\n *\n * Handles quoted values, expression values (i.e., `={...}`), and bare attributes, including optional\n * `:` or `v-bind:` prefixes. The detection accounts for whitespace before the attribute, as well as attributes\n * written on their own line. If an attribute value is an expression (`={...}`), this function uses\n * {@link findExpressionEnd} to locate the closing brace, and gracefully skips unbalanced or invalid expressions.\n *\n * @remarks\n * The returned ranges are suitable for text replacements: all whitespace before the attribute is included\n * so that removing the attribute does not leave a dangling blank line or extra spaces.\n *\n * @param input - The input markup string to search for the attribute within.\n * @param attribute - The name of the attribute to locate (e.g., `\"data-testid\"`).\n *\n * @returns An array of `[start, end)` ranges (as type `Range`) for each found occurrence of the attribute.\n */\nfunction findAttributeRangesFor(input: string, attribute: string): Range[] {\n\tconst NAME = escapeRegExp(attribute);\n\t// The leading `\\s+` swallows all whitespace before the attribute, so an attribute on its own line leaves no\n\t// blank line behind. Three forms follow: quoted value (group 1 = quote), expression value (group 2 = opening\n\t// brace, measured by `findExpressionEnd`), or bare attribute followed by whitespace, `/` or `>`\n\t// eslint-disable-next-line security/detect-non-literal-regexp -- the attribute name is regex-escaped above\n\tconst PATTERN = new RegExp(\n\t\t`\\\\s+(?::|v-bind:)?${NAME}(?:\\\\s*=\\\\s*(?:(['\"\\`])(?:(?!\\\\1)[\\\\s\\\\S])*\\\\1|(\\\\{))|(?=[\\\\s/>]))`,\n\t\t'gi',\n\t);\n\tconst RANGES: Range[] = [];\n\n\tlet match = PATTERN.exec(input);\n\n\twhile (match !== null) {\n\t\tconst MATCH_END = match.index + match[0].length;\n\n\t\tlet attributeEnd = MATCH_END;\n\n\t\tif (match[2] !== undefined) {\n\t\t\tconst CLOSING_BRACE_INDEX = findExpressionEnd(input, MATCH_END - 1);\n\n\t\t\t// An unbalanced expression means the source does not parse as written: leave it alone rather than\n\t\t\t// cutting the file at an arbitrary point\n\t\t\tif (CLOSING_BRACE_INDEX === -1) {\n\t\t\t\tmatch = PATTERN.exec(input);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tattributeEnd = CLOSING_BRACE_INDEX + 1;\n\t\t}\n\n\t\tRANGES.push([match.index, attributeEnd]);\n\t\tPATTERN.lastIndex = attributeEnd;\n\t\tmatch = PATTERN.exec(input);\n\t}\n\n\treturn RANGES;\n}\n\n/**\n * Returns the merged ranges of every occurrence of the given attributes (in quoted, `={expression}`,\n * or bare form, with optional `:` or `v-bind:` prefix) within the markup string. The resulting\n * ranges are sorted by start index, and any touching or overlapping ranges are merged.\n *\n * All attributes are located in the same original input, so the returned ranges can be used\n * for sourcemap-aware or batch editing operations.\n *\n * @param input - The markup string to search for the attributes.\n * @param attributes - An array of attribute names to search for (e.g., `['data-testid', 'data-id']`).\n *\n * @returns An array of `Range` tuples, each representing [start, end) of a matched attribute occurrence,\n * with adjacent and overlapping ranges merged.\n */\nfunction findAttributeRanges(input: string, attributes: string[]): Range[] {\n\tconst RANGES = attributes\n\t\t.flatMap((attribute) => findAttributeRangesFor(input, attribute))\n\t\t.sort((a, b) => a[0] - b[0] || a[1] - b[1]);\n\tconst MERGED: [number, number][] = [];\n\n\tfor (const [START, END] of RANGES) {\n\t\tconst LAST = MERGED.at(-1);\n\n\t\tif (LAST && START <= LAST[1]) {\n\t\t\tLAST[1] = Math.max(LAST[1], END);\n\t\t} else {\n\t\t\tMERGED.push([START, END]);\n\t\t}\n\t}\n\n\treturn MERGED;\n}\n\n/**\n * Returns a copy of the input string with the specified ranges removed.\n *\n * The provided ranges must be sorted by start index and non-overlapping.\n * Each range is specified as a tuple [start, end), with `start` inclusive and `end` exclusive.\n * The function concatenates the segments of the input string outside of these ranges.\n *\n * @param input - The original string from which to remove segments.\n * @param ranges - An array of `[start, end)` index ranges (sorted, non-overlapping) to cut out from the input.\n *\n * @returns The resulting string after all specified ranges have been removed from the input.\n */\nfunction removeRanges(input: string, ranges: Range[]): string {\n\tif (ranges.length === 0) {\n\t\treturn input;\n\t}\n\n\tconst SEGMENTS: string[] = [];\n\n\tlet lastEnd = 0;\n\n\tfor (const [START, END] of ranges) {\n\t\tSEGMENTS.push(input.slice(lastEnd, START));\n\t\tlastEnd = END;\n\t}\n\n\tSEGMENTS.push(input.slice(lastEnd));\n\n\treturn SEGMENTS.join('');\n}\n\n/**\n * Removes all occurrences of the specified attributes from the provided markup string.\n *\n * This function finds every instance of the given attribute names within the input markup (using\n * {@link findAttributeRanges}), then removes them, returning the markup with those attributes omitted.\n *\n * @param input - The original markup string from which attributes should be removed.\n * @param attributes - An array of attribute names to remove from the markup.\n *\n * @returns A new string representing the markup with the specified attributes removed.\n *\n * @see findAttributeRanges\n */\nfunction removeAttributes(input: string, attributes: string[]): string {\n\treturn removeRanges(input, findAttributeRanges(input, attributes));\n}\n\nexport type { Range };\nexport {\n\tcleanIgnoredPath,\n\tcleanIgnoredPaths,\n\tDEFAULT_IGNORE_PATHS,\n\tescapeRegExp,\n\tfindAttributeRanges,\n\tfindExpressionEnd,\n\tgetIgnoredPaths,\n\tgetOptions,\n\thasExtension,\n\thasIgnorePath,\n\tremoveAttributes,\n\tremoveRanges,\n\tstripQuery,\n\ttoRelativePath,\n};\n","import type { Plugin } from 'vite';\nimport type { Options } from './types';\n\nimport { generateRemovalSourceMap } from './sourcemap';\nimport {\n\tfindAttributeRanges,\n\tgetIgnoredPaths,\n\tgetOptions,\n\thasExtension,\n\thasIgnorePath,\n\tremoveRanges,\n\tstripQuery,\n\ttoRelativePath,\n} from './utilities';\n\nexport type { Options } from './types';\n\n/**\n * Vite plugin to remove specified attributes from markup files.\n *\n * This plugin scans files with configured extensions and removes\n * attributes as defined in the option list. It also produces\n * accurate source maps to ensure the original line and column numbers\n * are preserved for consumers of the Vite build chain.\n *\n * Files can be ignored based on path token matching. Ignore patterns\n * are resolved relative to the Vite root.\n *\n * @param options - Plugin configuration including which attributes\n * and file extensions to target, and optional ignore paths.\n *\n * @returns A Vite plugin object that removes attributes during\n * transformation steps.\n */\nfunction removeAttributesPlugin(options: Options): Plugin {\n\tconst OPTIONS = getOptions(options);\n\tconst IGNORED_PATHS = getIgnoredPaths(OPTIONS);\n\n\t// Ignore tokens are matched against paths relative to the Vite root, not to the process working directory\n\tlet root = process.cwd();\n\n\treturn {\n\t\tname: 'remove-attributes',\n\t\tenforce: 'pre',\n\t\tconfigResolved(config) {\n\t\t\troot = config.root;\n\t\t},\n\t\ttransform(code, id) {\n\t\t\t// Virtual modules (`\\0…`) never carry markup\n\t\t\tif (\n\t\t\t\tid.startsWith('\\0') ||\n\t\t\t\t!hasExtension(id, OPTIONS.extensions) ||\n\t\t\t\thasIgnorePath(toRelativePath(id, root), IGNORED_PATHS)\n\t\t\t) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst RANGES = findAttributeRanges(code, OPTIONS.attributes);\n\n\t\t\tif (RANGES.length === 0) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// A removal shifts every later column (and every later line when the attribute sat on its own line), so the\n\t\t\t// map keeps the consumer's sourcemaps pointing at the original positions\n\t\t\treturn { code: removeRanges(code, RANGES), map: generateRemovalSourceMap(code, RANGES, stripQuery(id)) };\n\t\t},\n\t};\n}\n\nexport default removeAttributesPlugin;\n"],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import { Range } from './utilities.cjs';
2
+ /**
3
+ * Source map v3 object, as accepted by Vite's `transform` hook (`map`)
4
+ */
5
+ interface SourceMap {
6
+ version: 3;
7
+ sources: string[];
8
+ sourcesContent: string[];
9
+ names: string[];
10
+ mappings: string;
11
+ }
12
+ /**
13
+ * Encodes a single integer value as a base64 VLQ (Variable Length Quantity) string.
14
+ *
15
+ * @remarks
16
+ * Base64 VLQ is the encoding format used for numbers in source map `mappings` fields.
17
+ * This function encodes both positive and negative integers using bitwise operations,
18
+ * setting the least significant bit for sign and using continuation bits for multi-digit numbers.
19
+ *
20
+ * @param value - The integer to encode. Can be positive, negative, or zero.
21
+ *
22
+ * @returns The VLQ Base64-encoded string representation.
23
+ *
24
+ * @see [Source Map V3 Spec](https://sourcemaps.info/spec.html)
25
+ */
26
+ declare function encodeVlq(value: number): string;
27
+ /**
28
+ * Generates a source map for a given `source` string with the specified ranges removed.
29
+ *
30
+ * Emits a segment at the start of each generated line, at each position where the original text was cut, and at
31
+ * every word boundary in between, allowing consumers to resolve any token to its original line and column.
32
+ *
33
+ * @param source - The original source code string.
34
+ * @param ranges - Sorted, non-overlapping `[start, end)` character ranges to be removed.
35
+ * @param file - The original file name or path. Used as the single source entry in the resulting source map.
36
+ *
37
+ * @returns The generated SourceMap object with correct mappings after range removal.
38
+ */
39
+ declare function generateRemovalSourceMap(source: string, ranges: Range[], file: string): SourceMap;
40
+ export type { SourceMap };
41
+ export { encodeVlq, generateRemovalSourceMap };
@@ -0,0 +1,41 @@
1
+ import { Range } from './utilities.js';
2
+ /**
3
+ * Source map v3 object, as accepted by Vite's `transform` hook (`map`)
4
+ */
5
+ interface SourceMap {
6
+ version: 3;
7
+ sources: string[];
8
+ sourcesContent: string[];
9
+ names: string[];
10
+ mappings: string;
11
+ }
12
+ /**
13
+ * Encodes a single integer value as a base64 VLQ (Variable Length Quantity) string.
14
+ *
15
+ * @remarks
16
+ * Base64 VLQ is the encoding format used for numbers in source map `mappings` fields.
17
+ * This function encodes both positive and negative integers using bitwise operations,
18
+ * setting the least significant bit for sign and using continuation bits for multi-digit numbers.
19
+ *
20
+ * @param value - The integer to encode. Can be positive, negative, or zero.
21
+ *
22
+ * @returns The VLQ Base64-encoded string representation.
23
+ *
24
+ * @see [Source Map V3 Spec](https://sourcemaps.info/spec.html)
25
+ */
26
+ declare function encodeVlq(value: number): string;
27
+ /**
28
+ * Generates a source map for a given `source` string with the specified ranges removed.
29
+ *
30
+ * Emits a segment at the start of each generated line, at each position where the original text was cut, and at
31
+ * every word boundary in between, allowing consumers to resolve any token to its original line and column.
32
+ *
33
+ * @param source - The original source code string.
34
+ * @param ranges - Sorted, non-overlapping `[start, end)` character ranges to be removed.
35
+ * @param file - The original file name or path. Used as the single source entry in the resulting source map.
36
+ *
37
+ * @returns The generated SourceMap object with correct mappings after range removal.
38
+ */
39
+ declare function generateRemovalSourceMap(source: string, ranges: Range[], file: string): SourceMap;
40
+ export type { SourceMap };
41
+ export { encodeVlq, generateRemovalSourceMap };
@@ -0,0 +1,30 @@
1
+ interface Options {
2
+ /**
3
+ * File extensions to process, without the leading dot (e.g. `['svelte', 'vue', 'ts']`)
4
+ */
5
+ extensions: string[];
6
+ /**
7
+ * Attribute names to remove (e.g. `['data-testid']`)
8
+ */
9
+ attributes: string[];
10
+ /**
11
+ * Folders to skip, relative to the Vite root (e.g. `['src/tests', 'fixtures']`)
12
+ *
13
+ * A token matches on path-segment boundaries only: `build` matches `build/app.js` but not `buildhome/app.js`.
14
+ * `*` matches any characters within a single segment (e.g. `*.stories`).
15
+ */
16
+ ignoreFolders?: string[];
17
+ /**
18
+ * Files to skip, relative to the Vite root (e.g. `['Header.svelte', 'src/components/Modal.svelte']`)
19
+ *
20
+ * Same matching rules as `ignoreFolders`.
21
+ */
22
+ ignoreFiles?: string[];
23
+ /**
24
+ * Apply the built-in ignore list (`node_modules`, `.git`, `build`, `dist`, `public`, `.svelte-kit`, …) on top of
25
+ * `ignoreFolders` / `ignoreFiles`. Default: `true`
26
+ */
27
+ ignoreDefaults?: boolean;
28
+ }
29
+ type ResolvedOptions = Required<Options>;
30
+ export type { Options, ResolvedOptions };
package/dist/types.d.ts CHANGED
@@ -1,8 +1,30 @@
1
1
  interface Options {
2
+ /**
3
+ * File extensions to process, without the leading dot (e.g. `['svelte', 'vue', 'ts']`)
4
+ */
2
5
  extensions: string[];
6
+ /**
7
+ * Attribute names to remove (e.g. `['data-testid']`)
8
+ */
3
9
  attributes: string[];
10
+ /**
11
+ * Folders to skip, relative to the Vite root (e.g. `['src/tests', 'fixtures']`)
12
+ *
13
+ * A token matches on path-segment boundaries only: `build` matches `build/app.js` but not `buildhome/app.js`.
14
+ * `*` matches any characters within a single segment (e.g. `*.stories`).
15
+ */
4
16
  ignoreFolders?: string[];
17
+ /**
18
+ * Files to skip, relative to the Vite root (e.g. `['Header.svelte', 'src/components/Modal.svelte']`)
19
+ *
20
+ * Same matching rules as `ignoreFolders`.
21
+ */
5
22
  ignoreFiles?: string[];
23
+ /**
24
+ * Apply the built-in ignore list (`node_modules`, `.git`, `build`, `dist`, `public`, `.svelte-kit`, …) on top of
25
+ * `ignoreFolders` / `ignoreFiles`. Default: `true`
26
+ */
27
+ ignoreDefaults?: boolean;
6
28
  }
7
- export type { Options };
8
- //# sourceMappingURL=types.d.ts.map
29
+ type ResolvedOptions = Required<Options>;
30
+ export type { Options, ResolvedOptions };
@@ -0,0 +1,156 @@
1
+ import { Options, ResolvedOptions } from './types.cjs';
2
+ declare const DEFAULT_IGNORE_PATHS: string[];
3
+ declare function escapeRegExp(value: string): string;
4
+ declare function getOptions(options: Options): ResolvedOptions;
5
+ /**
6
+ * Normalizes a user-supplied ignore token by trimming whitespace,
7
+ * removing leading './' or '/', and stripping trailing '/' characters.
8
+ *
9
+ * @param path - The ignore token to normalize.
10
+ *
11
+ * @returns The normalized ignore token.
12
+ */
13
+ declare function cleanIgnoredPath(path: string): string;
14
+ /**
15
+ * Normalizes and de-duplicates ignore tokens by:
16
+ * - Trimming whitespace from each path
17
+ * - Removing leading './' or '/', and trailing '/' characters
18
+ * - Dropping empty and root-only entries (such as `''`, `.`, `/`, and `./`)
19
+ * - Removing duplicate entries in the result
20
+ *
21
+ * @param paths - An array of ignore path tokens to be cleaned and de-duplicated.
22
+ *
23
+ * @returns A new array containing unique, cleaned ignore tokens, with all empty or root-only entries omitted.
24
+ */
25
+ declare function cleanIgnoredPaths(paths: string[]): string[];
26
+ /**
27
+ * Returns the combined and cleaned list of ignored paths based on the provided {@link ResolvedOptions}.
28
+ *
29
+ * @remarks
30
+ * - Merges `ignoreFolders` and `ignoreFiles` into a single list.
31
+ * - Cleans each entry using {@link cleanIgnoredPaths}.
32
+ * - If `ignoreDefaults` is not explicitly set to `false`, the list also includes {@link DEFAULT_IGNORE_PATHS}.
33
+ * - The resulting list is de-duplicated.
34
+ *
35
+ * @param options - The resolved options containing `ignoreFolders`, `ignoreFiles` and the `ignoreDefaults` flag.
36
+ *
37
+ * @returns An array of unique, cleaned ignore path tokens, including the built-in defaults unless
38
+ * {@link ResolvedOptions.ignoreDefaults} is `false`.
39
+ */
40
+ declare function getIgnoredPaths(options: ResolvedOptions): string[];
41
+ /**
42
+ * Removes the `?query` suffix that Vite appends to module IDs.
43
+ *
44
+ * For example, given `/src/App.svelte?svelte&type=style&lang.css`, this function will return `/src/App.svelte`.
45
+ *
46
+ * @param id - The module ID which may include a `?query` suffix.
47
+ *
48
+ * @returns The module ID without any `?query` part.
49
+ */
50
+ declare function stripQuery(id: string): string;
51
+ /**
52
+ * Returns the path of a module ID relative to the Vite root directory, using POSIX separators.
53
+ *
54
+ * @param id - The module ID to be converted to a relative path.
55
+ * @param root - The root directory to which the path will be made relative.
56
+ *
57
+ * @returns The relative path from the root to the module ID, using '/' as the separator.
58
+ */
59
+ declare function toRelativePath(id: string, root: string): string;
60
+ /**
61
+ * Checks if the given relative path matches any of the provided ignore tokens,
62
+ * comparing on path-segment boundaries. Ignores leading parent directory segments.
63
+ *
64
+ * @remarks
65
+ * This function ignores absolute IDs to avoid false positives from folder names that coincidentally
66
+ * contain ignore tokens. For example, in environments like Cloudflare where a repo may be cloned
67
+ * to a directory such as `/opt/buildhome/repo`, a token like `build` should not match simply
68
+ * because it's part of the parent path. Only the path relative to the Vite root is considered.
69
+ *
70
+ * Leading `../` segments are stripped from the path so that modules resolved outside the root
71
+ * (e.g. `../../.pnpm/x/node_modules/y/index.js`) still have the opportunity to match ignore tokens
72
+ * like `node_modules` against their segments.
73
+ *
74
+ * @param relativePath - The path of the module relative to the Vite root.
75
+ * @param tokens - The list of ignore tokens, possibly including `*` wildcards.
76
+ *
77
+ * @returns `true` if the relative path matches any ignore token; otherwise, `false`.
78
+ */
79
+ declare function hasIgnorePath(relativePath: string, tokens: string[]): boolean;
80
+ /**
81
+ * Determines whether the provided module id (with query suffix stripped) ends with one of the specified extensions.
82
+ *
83
+ * @param id - The module identifier, possibly including a query suffix (e.g., `file.js?raw`).
84
+ * @param extensions - An array of file extension strings (with or without leading dots).
85
+ *
86
+ * @returns `true` if the module id (excluding the query suffix) ends with one of the provided extensions;
87
+ * otherwise, `false`.
88
+ */
89
+ declare function hasExtension(id: string, extensions: string[]): boolean;
90
+ /**
91
+ * Finds the closing brace for an `={…}` attribute value in a string of markup. Returns the index of the closing
92
+ * brace, or `-1` if the braces are unbalanced or not found.
93
+ *
94
+ * @remarks
95
+ * A regular expression cannot do this: the expression may hold a template literal whose `${…}` placeholders nest
96
+ * further braces, and a brace inside a string is text rather than structure. The scanner tracks both.
97
+ *
98
+ * @param input - The input string containing the markup or code.
99
+ * @param openingBraceIndex - The index of the opening `{` character to start scanning from.
100
+ *
101
+ * @returns The index of the corresponding closing `}` brace, or `-1` if unmatched.
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * findExpressionEnd('foo={a + {b: `1${two}`}}', 4); // returns the index of the matching }
106
+ * ```
107
+ */
108
+ declare function findExpressionEnd(input: string, openingBraceIndex: number): number;
109
+ /**
110
+ * Half-open `[start, end)` range of characters in the original input
111
+ */
112
+ type Range = readonly [start: number, end: number];
113
+ /**
114
+ * Returns the merged ranges of every occurrence of the given attributes (in quoted, `={expression}`,
115
+ * or bare form, with optional `:` or `v-bind:` prefix) within the markup string. The resulting
116
+ * ranges are sorted by start index, and any touching or overlapping ranges are merged.
117
+ *
118
+ * All attributes are located in the same original input, so the returned ranges can be used
119
+ * for sourcemap-aware or batch editing operations.
120
+ *
121
+ * @param input - The markup string to search for the attributes.
122
+ * @param attributes - An array of attribute names to search for (e.g., `['data-testid', 'data-id']`).
123
+ *
124
+ * @returns An array of `Range` tuples, each representing [start, end) of a matched attribute occurrence,
125
+ * with adjacent and overlapping ranges merged.
126
+ */
127
+ declare function findAttributeRanges(input: string, attributes: string[]): Range[];
128
+ /**
129
+ * Returns a copy of the input string with the specified ranges removed.
130
+ *
131
+ * The provided ranges must be sorted by start index and non-overlapping.
132
+ * Each range is specified as a tuple [start, end), with `start` inclusive and `end` exclusive.
133
+ * The function concatenates the segments of the input string outside of these ranges.
134
+ *
135
+ * @param input - The original string from which to remove segments.
136
+ * @param ranges - An array of `[start, end)` index ranges (sorted, non-overlapping) to cut out from the input.
137
+ *
138
+ * @returns The resulting string after all specified ranges have been removed from the input.
139
+ */
140
+ declare function removeRanges(input: string, ranges: Range[]): string;
141
+ /**
142
+ * Removes all occurrences of the specified attributes from the provided markup string.
143
+ *
144
+ * This function finds every instance of the given attribute names within the input markup (using
145
+ * {@link findAttributeRanges}), then removes them, returning the markup with those attributes omitted.
146
+ *
147
+ * @param input - The original markup string from which attributes should be removed.
148
+ * @param attributes - An array of attribute names to remove from the markup.
149
+ *
150
+ * @returns A new string representing the markup with the specified attributes removed.
151
+ *
152
+ * @see findAttributeRanges
153
+ */
154
+ declare function removeAttributes(input: string, attributes: string[]): string;
155
+ export type { Range };
156
+ export { cleanIgnoredPath, cleanIgnoredPaths, DEFAULT_IGNORE_PATHS, escapeRegExp, findAttributeRanges, findExpressionEnd, getIgnoredPaths, getOptions, hasExtension, hasIgnorePath, removeAttributes, removeRanges, stripQuery, toRelativePath, };
@@ -1,12 +1,156 @@
1
- import type { Options } from './types';
2
- export declare function getOptions(options: Options): Options;
3
- export declare function getIgnoredPaths(options: Options): string[];
4
- export declare function isString(path: string): boolean;
5
- export declare function cleanString(path: string): string;
6
- export declare function hasIgnorePath(id: string, paths?: string[]): boolean;
7
- export declare function cleanIgnoredPaths(paths: string[], inValidPaths: string[]): string[];
8
- export declare function isFile(path: string): boolean;
9
- export declare function isFolder(path: string): boolean;
10
- export declare function hasExtension(id: string, options: Options): boolean;
11
- export declare function removeAttributes(input: string, attributes: string[]): string;
12
- //# sourceMappingURL=utilities.d.ts.map
1
+ import { Options, ResolvedOptions } from './types.js';
2
+ declare const DEFAULT_IGNORE_PATHS: string[];
3
+ declare function escapeRegExp(value: string): string;
4
+ declare function getOptions(options: Options): ResolvedOptions;
5
+ /**
6
+ * Normalizes a user-supplied ignore token by trimming whitespace,
7
+ * removing leading './' or '/', and stripping trailing '/' characters.
8
+ *
9
+ * @param path - The ignore token to normalize.
10
+ *
11
+ * @returns The normalized ignore token.
12
+ */
13
+ declare function cleanIgnoredPath(path: string): string;
14
+ /**
15
+ * Normalizes and de-duplicates ignore tokens by:
16
+ * - Trimming whitespace from each path
17
+ * - Removing leading './' or '/', and trailing '/' characters
18
+ * - Dropping empty and root-only entries (such as `''`, `.`, `/`, and `./`)
19
+ * - Removing duplicate entries in the result
20
+ *
21
+ * @param paths - An array of ignore path tokens to be cleaned and de-duplicated.
22
+ *
23
+ * @returns A new array containing unique, cleaned ignore tokens, with all empty or root-only entries omitted.
24
+ */
25
+ declare function cleanIgnoredPaths(paths: string[]): string[];
26
+ /**
27
+ * Returns the combined and cleaned list of ignored paths based on the provided {@link ResolvedOptions}.
28
+ *
29
+ * @remarks
30
+ * - Merges `ignoreFolders` and `ignoreFiles` into a single list.
31
+ * - Cleans each entry using {@link cleanIgnoredPaths}.
32
+ * - If `ignoreDefaults` is not explicitly set to `false`, the list also includes {@link DEFAULT_IGNORE_PATHS}.
33
+ * - The resulting list is de-duplicated.
34
+ *
35
+ * @param options - The resolved options containing `ignoreFolders`, `ignoreFiles` and the `ignoreDefaults` flag.
36
+ *
37
+ * @returns An array of unique, cleaned ignore path tokens, including the built-in defaults unless
38
+ * {@link ResolvedOptions.ignoreDefaults} is `false`.
39
+ */
40
+ declare function getIgnoredPaths(options: ResolvedOptions): string[];
41
+ /**
42
+ * Removes the `?query` suffix that Vite appends to module IDs.
43
+ *
44
+ * For example, given `/src/App.svelte?svelte&type=style&lang.css`, this function will return `/src/App.svelte`.
45
+ *
46
+ * @param id - The module ID which may include a `?query` suffix.
47
+ *
48
+ * @returns The module ID without any `?query` part.
49
+ */
50
+ declare function stripQuery(id: string): string;
51
+ /**
52
+ * Returns the path of a module ID relative to the Vite root directory, using POSIX separators.
53
+ *
54
+ * @param id - The module ID to be converted to a relative path.
55
+ * @param root - The root directory to which the path will be made relative.
56
+ *
57
+ * @returns The relative path from the root to the module ID, using '/' as the separator.
58
+ */
59
+ declare function toRelativePath(id: string, root: string): string;
60
+ /**
61
+ * Checks if the given relative path matches any of the provided ignore tokens,
62
+ * comparing on path-segment boundaries. Ignores leading parent directory segments.
63
+ *
64
+ * @remarks
65
+ * This function ignores absolute IDs to avoid false positives from folder names that coincidentally
66
+ * contain ignore tokens. For example, in environments like Cloudflare where a repo may be cloned
67
+ * to a directory such as `/opt/buildhome/repo`, a token like `build` should not match simply
68
+ * because it's part of the parent path. Only the path relative to the Vite root is considered.
69
+ *
70
+ * Leading `../` segments are stripped from the path so that modules resolved outside the root
71
+ * (e.g. `../../.pnpm/x/node_modules/y/index.js`) still have the opportunity to match ignore tokens
72
+ * like `node_modules` against their segments.
73
+ *
74
+ * @param relativePath - The path of the module relative to the Vite root.
75
+ * @param tokens - The list of ignore tokens, possibly including `*` wildcards.
76
+ *
77
+ * @returns `true` if the relative path matches any ignore token; otherwise, `false`.
78
+ */
79
+ declare function hasIgnorePath(relativePath: string, tokens: string[]): boolean;
80
+ /**
81
+ * Determines whether the provided module id (with query suffix stripped) ends with one of the specified extensions.
82
+ *
83
+ * @param id - The module identifier, possibly including a query suffix (e.g., `file.js?raw`).
84
+ * @param extensions - An array of file extension strings (with or without leading dots).
85
+ *
86
+ * @returns `true` if the module id (excluding the query suffix) ends with one of the provided extensions;
87
+ * otherwise, `false`.
88
+ */
89
+ declare function hasExtension(id: string, extensions: string[]): boolean;
90
+ /**
91
+ * Finds the closing brace for an `={…}` attribute value in a string of markup. Returns the index of the closing
92
+ * brace, or `-1` if the braces are unbalanced or not found.
93
+ *
94
+ * @remarks
95
+ * A regular expression cannot do this: the expression may hold a template literal whose `${…}` placeholders nest
96
+ * further braces, and a brace inside a string is text rather than structure. The scanner tracks both.
97
+ *
98
+ * @param input - The input string containing the markup or code.
99
+ * @param openingBraceIndex - The index of the opening `{` character to start scanning from.
100
+ *
101
+ * @returns The index of the corresponding closing `}` brace, or `-1` if unmatched.
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * findExpressionEnd('foo={a + {b: `1${two}`}}', 4); // returns the index of the matching }
106
+ * ```
107
+ */
108
+ declare function findExpressionEnd(input: string, openingBraceIndex: number): number;
109
+ /**
110
+ * Half-open `[start, end)` range of characters in the original input
111
+ */
112
+ type Range = readonly [start: number, end: number];
113
+ /**
114
+ * Returns the merged ranges of every occurrence of the given attributes (in quoted, `={expression}`,
115
+ * or bare form, with optional `:` or `v-bind:` prefix) within the markup string. The resulting
116
+ * ranges are sorted by start index, and any touching or overlapping ranges are merged.
117
+ *
118
+ * All attributes are located in the same original input, so the returned ranges can be used
119
+ * for sourcemap-aware or batch editing operations.
120
+ *
121
+ * @param input - The markup string to search for the attributes.
122
+ * @param attributes - An array of attribute names to search for (e.g., `['data-testid', 'data-id']`).
123
+ *
124
+ * @returns An array of `Range` tuples, each representing [start, end) of a matched attribute occurrence,
125
+ * with adjacent and overlapping ranges merged.
126
+ */
127
+ declare function findAttributeRanges(input: string, attributes: string[]): Range[];
128
+ /**
129
+ * Returns a copy of the input string with the specified ranges removed.
130
+ *
131
+ * The provided ranges must be sorted by start index and non-overlapping.
132
+ * Each range is specified as a tuple [start, end), with `start` inclusive and `end` exclusive.
133
+ * The function concatenates the segments of the input string outside of these ranges.
134
+ *
135
+ * @param input - The original string from which to remove segments.
136
+ * @param ranges - An array of `[start, end)` index ranges (sorted, non-overlapping) to cut out from the input.
137
+ *
138
+ * @returns The resulting string after all specified ranges have been removed from the input.
139
+ */
140
+ declare function removeRanges(input: string, ranges: Range[]): string;
141
+ /**
142
+ * Removes all occurrences of the specified attributes from the provided markup string.
143
+ *
144
+ * This function finds every instance of the given attribute names within the input markup (using
145
+ * {@link findAttributeRanges}), then removes them, returning the markup with those attributes omitted.
146
+ *
147
+ * @param input - The original markup string from which attributes should be removed.
148
+ * @param attributes - An array of attribute names to remove from the markup.
149
+ *
150
+ * @returns A new string representing the markup with the specified attributes removed.
151
+ *
152
+ * @see findAttributeRanges
153
+ */
154
+ declare function removeAttributes(input: string, attributes: string[]): string;
155
+ export type { Range };
156
+ export { cleanIgnoredPath, cleanIgnoredPaths, DEFAULT_IGNORE_PATHS, escapeRegExp, findAttributeRanges, findExpressionEnd, getIgnoredPaths, getOptions, hasExtension, hasIgnorePath, removeAttributes, removeRanges, stripQuery, toRelativePath, };