fast-ignore
Advanced tools
Comparing version 1.0.0 to 1.1.0
import type { Glob, Node, Options } from '../types'; | ||
declare const compile: (globs: Glob[], options: Options) => Node; | ||
declare const compile: (tiers: Glob[][], options: Options) => Node; | ||
export default compile; |
/* IMPORT */ | ||
import matcher from '../glob/matcher.js'; | ||
/* MAIN */ | ||
const compile = (globs, options) => { | ||
const compile = (tiers, options) => { | ||
const caseSensitive = options.caseSensitive ?? false; | ||
const root = { id: '', globstar: false, negative: false, strength: -1, match: () => false, children: [] }; | ||
for (let gi = 0, gl = globs.length; gi < gl; gi++) { | ||
let content = globs[gi].content; | ||
let parent = root; | ||
content = content.replace(/\/$/, ''); //TODO: Handle this instead | ||
content = content.replace(/(^|\/)\*\*\/(?:\*\*(\/|$))+/g, '$1**$2'); | ||
content = content.startsWith('/') ? content.slice(1) : (content.startsWith('**/') || content.slice(0, -1).includes('/') ? content : `**/${content}`); | ||
const segments = content.split('/'); | ||
for (let si = 0, sl = segments.length; si < sl; si++) { | ||
const id = segments[si]; | ||
const globstar = (id === '**'); | ||
const terminal = (si === sl - 1); | ||
const negative = globs[gi].negative; | ||
const strength = (terminal ? globs[gi].strength : -1); | ||
const match = matcher(id, caseSensitive); | ||
const children = []; | ||
const node = { id, globstar, negative, strength, match, children }; | ||
const nodeExisting = parent.children.find(node => node.id === id); | ||
if (nodeExisting) { | ||
if (strength >= nodeExisting.strength) { // Existing weaker node, overriding it | ||
nodeExisting.negative = negative; | ||
nodeExisting.strength = strength; | ||
const root = { id: '', globstar: false, negative: false, strength: -1, tier: -1, match: () => false, children: [] }; | ||
let scounter = 0; | ||
for (let ti = 0, tl = tiers.length; ti < tl; ti++) { | ||
const globs = tiers[ti]; | ||
const tier = ti; | ||
for (let gi = 0, gl = globs.length; gi < gl; gi++) { | ||
let content = globs[gi].content; | ||
let parent = root; | ||
content = content.replace(/\/$/, ''); //TODO: Handle this instead | ||
content = content.replace(/(^|\/)\*\*\/(?:\*\*(\/|$))+/g, '$1**$2'); | ||
content = content.startsWith('/') ? content.slice(1) : (content.startsWith('**/') || content.slice(0, -1).includes('/') ? content : `**/${content}`); | ||
const segments = content.split('/'); | ||
for (let si = 0, sl = segments.length; si < sl; si++) { | ||
const id = segments[si]; | ||
const globstar = (id === '**'); | ||
const terminal = (si === sl - 1); | ||
const negative = globs[gi].negative; | ||
const strength = (terminal ? scounter++ : -1); | ||
const match = matcher(id, caseSensitive); | ||
const children = []; | ||
const node = { id, globstar, negative, strength, tier, match, children }; | ||
const nodeExisting = parent.children.find(node => node.id === id); | ||
if (nodeExisting) { | ||
if ((tier === nodeExisting.tier && strength >= nodeExisting.strength) || (tier > nodeExisting.tier && (nodeExisting.strength < 0 || nodeExisting.negative))) { // Existing node, overridable by tier/strength/negativity // Basically we are making sure that files ignored in previous tiers can't be re-included back in later tiers | ||
nodeExisting.negative = negative; | ||
nodeExisting.strength = strength; | ||
nodeExisting.tier = tier; | ||
} | ||
parent = nodeExisting; | ||
} | ||
parent = nodeExisting; | ||
else { | ||
parent.children.push(node); | ||
parent = node; | ||
} | ||
} | ||
else { | ||
parent.children.push(node); | ||
parent = node; | ||
} | ||
} | ||
@@ -36,0 +42,0 @@ } |
import type { Options } from '../types'; | ||
declare const matcher: (ignore: string, options?: Options) => (fileRelativePath: string) => boolean; | ||
declare const matcher: (ignore: string | string[], options?: Options) => (fileRelativePath: string) => boolean; | ||
export default matcher; |
@@ -7,4 +7,5 @@ /* IMPORT */ | ||
const matcher = (ignore, options = {}) => { | ||
const globs = parse(ignore); | ||
const root = compile(globs, options); | ||
const ignores = Array.isArray(ignore) ? ignore : [ignore]; | ||
const tiers = ignores.map(parse); | ||
const root = compile(tiers, options); | ||
const cache = []; //TODO: What is this kind of cache called?? | ||
@@ -11,0 +12,0 @@ return (fileRelativePath) => { |
@@ -13,7 +13,6 @@ /* IMPORT */ | ||
const negative = content.startsWith('!'); | ||
const strength = i; | ||
content = content.replace(/^\\(!|#)/, '$1'); | ||
content = content.replace(/((?:\\\s)*)\s*$/, ($0, $1) => $1.replaceAll('\\', '')); | ||
content = negative ? content.slice(1) : content; | ||
const glob = { content, negative, strength }; | ||
const glob = { content, negative }; | ||
globs.push(glob); | ||
@@ -20,0 +19,0 @@ } |
type Glob = { | ||
content: string; | ||
negative: boolean; | ||
strength: number; | ||
}; | ||
@@ -11,2 +10,3 @@ type Node = { | ||
strength: number; | ||
tier: number; | ||
match: (segment: string) => boolean; | ||
@@ -13,0 +13,0 @@ children: Node[]; |
@@ -5,3 +5,3 @@ { | ||
"description": "A fast parser and processor for .gitignore files.", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"type": "module", | ||
@@ -8,0 +8,0 @@ "main": "dist/index.js", |
@@ -48,2 +48,24 @@ # Fast Ignore | ||
ignore ( 'dist/foo/bar.js' ); // true | ||
// We can also work with multiple ignore files at once, which is faster than handling them individually | ||
// This goes roughly as fast as just concatenating the files together, but with the semantics of separate files | ||
// The difference is that an ignore file that comes later won't be able to un-ignore something that was ignored by a previous one | ||
// As a result please note that writing `fastIgnore ([ 'dist', '!dist' ])` may work differently than you expected | ||
const prettierignore = ` | ||
**/test | ||
**/__test__ | ||
# The following globs are effectively useless | ||
# because they can't un-ignore something that was ignored by any previous ignore files | ||
!dist | ||
!node_modules | ||
`; | ||
const comboIgnore = fastIgnore ([ gitignore, prettierignore ]); | ||
comboIgnore ( 'foo/bar.js' ); // false | ||
comboIgnore ( 'node_modules/foo/bar.js' ); // true | ||
comboIgnore ( 'dist/foo/bar.js' ); // true | ||
comboIgnore ( 'test/foo/bar.js' ); // true | ||
``` | ||
@@ -50,0 +72,0 @@ |
@@ -9,47 +9,57 @@ | ||
const compile = ( globs: Glob[], options: Options ): Node => { | ||
const compile = ( tiers: Glob[][], options: Options ): Node => { | ||
const caseSensitive = options.caseSensitive ?? false; | ||
const root: Node = { id: '', globstar: false, negative: false, strength: -1, match: () => false, children: [] }; | ||
const root: Node = { id: '', globstar: false, negative: false, strength: -1, tier: -1, match: () => false, children: [] }; | ||
for ( let gi = 0, gl = globs.length; gi < gl; gi++ ) { | ||
let scounter = 0; | ||
let content = globs[gi].content; | ||
let parent = root; | ||
for ( let ti = 0, tl = tiers.length; ti < tl; ti++ ) { | ||
content = content.replace ( /\/$/, '' ); //TODO: Handle this instead | ||
content = content.replace ( /(^|\/)\*\*\/(?:\*\*(\/|$))+/g, '$1**$2' ); | ||
content = content.startsWith ( '/' ) ? content.slice ( 1 ) : ( content.startsWith ( '**/' ) || content.slice ( 0, -1 ).includes ( '/' ) ? content : `**/${content}` ); | ||
const globs = tiers[ti]; | ||
const tier = ti; | ||
const segments = content.split ( '/' ); | ||
for ( let gi = 0, gl = globs.length; gi < gl; gi++ ) { | ||
for ( let si = 0, sl = segments.length; si < sl; si++ ) { | ||
let content = globs[gi].content; | ||
let parent = root; | ||
const id = segments[si]; | ||
const globstar = ( id === '**' ); | ||
const terminal = ( si === sl - 1 ); | ||
const negative = globs[gi].negative; | ||
const strength = ( terminal ? globs[gi].strength : -1 ); | ||
const match = matcher ( id, caseSensitive ); | ||
const children: Node[] = []; | ||
const node = { id, globstar, negative, strength, match, children }; | ||
const nodeExisting = parent.children.find ( node => node.id === id ); | ||
content = content.replace ( /\/$/, '' ); //TODO: Handle this instead | ||
content = content.replace ( /(^|\/)\*\*\/(?:\*\*(\/|$))+/g, '$1**$2' ); | ||
content = content.startsWith ( '/' ) ? content.slice ( 1 ) : ( content.startsWith ( '**/' ) || content.slice ( 0, -1 ).includes ( '/' ) ? content : `**/${content}` ); | ||
if ( nodeExisting ) { | ||
const segments = content.split ( '/' ); | ||
if ( strength >= nodeExisting.strength ) { // Existing weaker node, overriding it | ||
for ( let si = 0, sl = segments.length; si < sl; si++ ) { | ||
nodeExisting.negative = negative; | ||
nodeExisting.strength = strength; | ||
const id = segments[si]; | ||
const globstar = ( id === '**' ); | ||
const terminal = ( si === sl - 1 ); | ||
const negative = globs[gi].negative; | ||
const strength = ( terminal ? scounter++ : -1 ); | ||
const match = matcher ( id, caseSensitive ); | ||
const children: Node[] = []; | ||
const node = { id, globstar, negative, strength, tier, match, children }; | ||
const nodeExisting = parent.children.find ( node => node.id === id ); | ||
} | ||
if ( nodeExisting ) { | ||
parent = nodeExisting; | ||
if ( ( tier === nodeExisting.tier && strength >= nodeExisting.strength ) || ( tier > nodeExisting.tier && ( nodeExisting.strength < 0 || nodeExisting.negative ) ) ) { // Existing node, overridable by tier/strength/negativity // Basically we are making sure that files ignored in previous tiers can't be re-included back in later tiers | ||
} else { | ||
nodeExisting.negative = negative; | ||
nodeExisting.strength = strength; | ||
nodeExisting.tier = tier; | ||
parent.children.push ( node ); | ||
} | ||
parent = node; | ||
parent = nodeExisting; | ||
} else { | ||
parent.children.push ( node ); | ||
parent = node; | ||
} | ||
} | ||
@@ -56,0 +66,0 @@ |
@@ -11,6 +11,7 @@ | ||
const matcher = ( ignore: string, options: Options = {} ): (( fileRelativePath: string ) => boolean) => { | ||
const matcher = ( ignore: string | string[], options: Options = {} ): (( fileRelativePath: string ) => boolean) => { | ||
const globs = parse ( ignore ); | ||
const root = compile ( globs, options ); | ||
const ignores = Array.isArray ( ignore ) ? ignore : [ignore]; | ||
const tiers = ignores.map ( parse ); | ||
const root = compile ( tiers, options ); | ||
const cache: [segment: string, [nodesNext: Node[], negative: boolean, strength: number]][] = []; //TODO: What is this kind of cache called?? | ||
@@ -17,0 +18,0 @@ |
@@ -21,3 +21,2 @@ | ||
const negative = content.startsWith ( '!' ); | ||
const strength = i; | ||
@@ -28,3 +27,3 @@ content = content.replace ( /^\\(!|#)/, '$1' ); | ||
const glob = { content, negative, strength }; | ||
const glob = { content, negative }; | ||
@@ -31,0 +30,0 @@ globs.push ( glob ); |
@@ -6,4 +6,3 @@ | ||
content: string, | ||
negative: boolean, | ||
strength: number | ||
negative: boolean | ||
}; | ||
@@ -16,2 +15,3 @@ | ||
strength: number, | ||
tier: number, | ||
match: ( segment: string ) => boolean, | ||
@@ -18,0 +18,0 @@ children: Node[] |
@@ -42,4 +42,14 @@ | ||
benchmark ({ | ||
name: '.gitignore + .prettierignore', | ||
name: '.gitignore + .prettierignore (individual)', | ||
fn: () => { | ||
const ignore1 = toIgnore ( gitignoreContent ); | ||
const ignore2 = toIgnore ( prettierignoreContent ); | ||
filesPaths.forEach ( ignore1 ); | ||
filesPaths.forEach ( ignore2 ); | ||
} | ||
}); | ||
benchmark ({ | ||
name: '.gitignore + .prettierignore (concat)', | ||
fn: () => { | ||
const ignore = toIgnore ( comboContent ); | ||
@@ -50,2 +60,10 @@ filesPaths.forEach ( ignore ); | ||
benchmark ({ | ||
name: '.gitignore + .prettierignore (combo)', | ||
fn: () => { | ||
const ignore = toIgnore ([ gitignoreContent, prettierignoreContent ]); | ||
filesPaths.forEach ( ignore ); | ||
} | ||
}); | ||
benchmark.summary (); |
@@ -107,3 +107,3 @@ | ||
it ( 'works', t => { | ||
it ( 'works with a single tier', t => { | ||
@@ -197,5 +197,34 @@ t.is ( ignore ( 'foo.js', 'foo.js' ), true ); | ||
// t.is ( ignore ( 'foo.js/', 'foo.js' ), true ); //TODO | ||
// t.is ( ignore ( 'foo.js/', 'deep/foo.js' ), true ); //TODO | ||
}); | ||
it ( 'works with multiple tiers', t => { | ||
t.is ( ignore ( ['foo.js', 'bar.js'], 'foo.js' ), true ); | ||
t.is ( ignore ( ['foo.js', 'bar.js'], 'bar.js' ), true ); | ||
t.is ( ignore ( ['foo.js', 'bar.js'], 'baz.js' ), false ); | ||
t.is ( ignore ( ['foo.js', '!foo.js'], 'foo.js' ), true ); | ||
t.is ( ignore ( ['foo.js', '!foo.js'], 'bar.js' ), false ); | ||
t.is ( ignore ( ['foo.js', '!foo.js'], 'baz.js' ), false ); | ||
t.is ( ignore ( ['foo.js\n!foo.js', 'foo.js'], 'foo.js' ), true ); | ||
t.is ( ignore ( ['foo.js\n!foo.js', 'foo.js'], 'bar.js' ), false ); | ||
t.is ( ignore ( ['foo.js\n!foo.js', 'foo.js'], 'baz.js' ), false ); | ||
t.is ( ignore ( ['foo.js\n!foo.js', 'bar.js'], 'foo.js' ), false ); | ||
t.is ( ignore ( ['foo.js\n!foo.js', 'bar.js'], 'bar.js' ), true ); | ||
t.is ( ignore ( ['foo.js\n!foo.js', 'bar.js'], 'baz.js' ), false ); | ||
t.is ( ignore ( ['deep\n!deep', 'deep/foo.js'], 'deep/foo.js' ), true ); | ||
t.is ( ignore ( ['deep\n!deep', 'deep/foo.js'], 'deeper/deep/foo.js' ), false ); | ||
t.is ( ignore ( ['deep\n!deep', 'deep/foo.js'], 'deeper/deep/bar.js' ), false ); | ||
t.is ( ignore ( ['deep', 'deep/foo.js'], 'deep/foo.js' ), true ); | ||
t.is ( ignore ( ['deep', 'deep/foo.js'], 'deeper/deep/foo.js' ), true ); | ||
t.is ( ignore ( ['deep', 'deep/foo.js'], 'deeper/deep/bar.js' ), true ); | ||
t.is ( ignore ( ['deep', '!deep/foo.js'], 'deep/foo.js' ), true ); | ||
t.is ( ignore ( ['deep', '!deep/foo.js'], 'deeper/deep/foo.js' ), true ); | ||
t.is ( ignore ( ['deep', '!deep/foo.js'], 'deeper/deep/bar.js' ), true ); | ||
}); | ||
@@ -202,0 +231,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
34172
725
75