html-validate 9.0.1 → 9.1.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.
package/dist/cjs/core.js CHANGED
@@ -1947,7 +1947,7 @@ const table = {
1947
1947
  "nth-child": nthChild,
1948
1948
  scope
1949
1949
  };
1950
- function factory(name, context) {
1950
+ function factory$1(name, context) {
1951
1951
  const fn = table[name];
1952
1952
  if (fn) {
1953
1953
  return fn.bind(context);
@@ -2046,7 +2046,7 @@ function isQuotationMark(ch) {
2046
2046
  function isPseudoElement(ch, buffer) {
2047
2047
  return ch === ":" && buffer === ":";
2048
2048
  }
2049
- function* splitPattern(pattern) {
2049
+ function* splitPattern$1(pattern) {
2050
2050
  if (pattern === "") {
2051
2051
  return;
2052
2052
  }
@@ -2148,7 +2148,7 @@ class PseudoClassMatcher extends Matcher {
2148
2148
  this.args = args;
2149
2149
  }
2150
2150
  match(node, context) {
2151
- const fn = factory(this.name, context);
2151
+ const fn = factory$1(this.name, context);
2152
2152
  return fn(node, this.args);
2153
2153
  }
2154
2154
  }
@@ -2166,7 +2166,7 @@ class Pattern {
2166
2166
  this.selector = pattern;
2167
2167
  this.combinator = parseCombinator(match.shift(), pattern);
2168
2168
  this.tagName = match.shift() || "*";
2169
- this.pattern = Array.from(splitPattern(match[0]), (it) => this.createMatcher(it));
2169
+ this.pattern = Array.from(splitPattern$1(match[0]), (it) => this.createMatcher(it));
2170
2170
  }
2171
2171
  match(node, context) {
2172
2172
  return node.is(this.tagName) && this.pattern.every((cur) => cur.match(node, context));
@@ -10733,24 +10733,59 @@ const presets = {
10733
10733
  "html-validate:standard": config
10734
10734
  };
10735
10735
 
10736
- function getNamedTransformerFromPlugin(name, plugins, pluginName, key) {
10737
- const plugin = plugins.find((cur) => cur.name === pluginName);
10738
- if (!plugin) {
10739
- throw new ConfigError(`No plugin named "${pluginName}" has been loaded`);
10736
+ class ResolvedConfig {
10737
+ metaTable;
10738
+ plugins;
10739
+ rules;
10740
+ transformers;
10741
+ /** The original data this resolved configuration was created from */
10742
+ original;
10743
+ /**
10744
+ * @internal
10745
+ */
10746
+ cache;
10747
+ /**
10748
+ * @internal
10749
+ */
10750
+ constructor({ metaTable, plugins, rules, transformers }, original) {
10751
+ this.metaTable = metaTable;
10752
+ this.plugins = plugins;
10753
+ this.rules = rules;
10754
+ this.transformers = transformers;
10755
+ this.cache = /* @__PURE__ */ new Map();
10756
+ this.original = original;
10740
10757
  }
10741
- if (!plugin.transformer) {
10742
- throw new ConfigError(`Plugin does not expose any transformers`);
10758
+ /**
10759
+ * Returns the (merged) configuration data used to create this resolved
10760
+ * configuration.
10761
+ */
10762
+ getConfigData() {
10763
+ return this.original;
10743
10764
  }
10744
- if (typeof plugin.transformer === "function") {
10745
- throw new ConfigError(
10746
- `Transformer "${name}" refers to named transformer but plugin exposes only unnamed, use "${pluginName}" instead.`
10747
- );
10765
+ getMetaTable() {
10766
+ return this.metaTable;
10748
10767
  }
10749
- const transformer = plugin.transformer[key];
10750
- if (!transformer) {
10751
- throw new ConfigError(`Plugin "${pluginName}" does not expose a transformer named "${key}".`);
10768
+ getPlugins() {
10769
+ return this.plugins;
10770
+ }
10771
+ getRules() {
10772
+ return this.rules;
10773
+ }
10774
+ /**
10775
+ * Returns true if a transformer matches given filename.
10776
+ *
10777
+ * @public
10778
+ */
10779
+ canTransform(filename) {
10780
+ return Boolean(this.findTransformer(filename));
10781
+ }
10782
+ /**
10783
+ * @internal
10784
+ */
10785
+ findTransformer(filename) {
10786
+ const match = this.transformers.find((entry) => entry.pattern.test(filename));
10787
+ return match ?? null;
10752
10788
  }
10753
- return transformer;
10754
10789
  }
10755
10790
 
10756
10791
  function haveResolver(key, value) {
@@ -10884,283 +10919,6 @@ function staticResolver(map = {}) {
10884
10919
  };
10885
10920
  }
10886
10921
 
10887
- function getTransformerFromModule(resolvers, name) {
10888
- return resolveTransformer(resolvers, name, { cache: true });
10889
- }
10890
-
10891
- function getUnnamedTransformerFromPlugin(name, plugin) {
10892
- if (!plugin.transformer) {
10893
- throw new ConfigError(`Plugin does not expose any transformers`);
10894
- }
10895
- if (typeof plugin.transformer !== "function") {
10896
- if (plugin.transformer.default) {
10897
- return plugin.transformer.default;
10898
- }
10899
- throw new ConfigError(
10900
- `Transformer "${name}" refers to unnamed transformer but plugin exposes only named.`
10901
- );
10902
- }
10903
- return plugin.transformer;
10904
- }
10905
-
10906
- const TRANSFORMER_API = {
10907
- VERSION: 1
10908
- };
10909
-
10910
- function validateTransformer(transformer) {
10911
- const version = transformer.api ?? 0;
10912
- if (version !== TRANSFORMER_API.VERSION) {
10913
- throw new ConfigError(
10914
- `Transformer uses API version ${String(version)} but only version ${String(TRANSFORMER_API.VERSION)} is supported`
10915
- );
10916
- }
10917
- }
10918
- function loadTransformerFunction(resolvers, name, plugins) {
10919
- const match = name.match(/(.*):(.*)/);
10920
- if (match) {
10921
- const [, pluginName, key] = match;
10922
- return getNamedTransformerFromPlugin(name, plugins, pluginName, key);
10923
- }
10924
- const plugin = plugins.find((cur) => cur.name === name);
10925
- if (plugin) {
10926
- return getUnnamedTransformerFromPlugin(name, plugin);
10927
- }
10928
- return getTransformerFromModule(resolvers, name);
10929
- }
10930
- function getTransformerFunction(resolvers, name, plugins) {
10931
- try {
10932
- const transformer = loadTransformerFunction(resolvers, name, plugins);
10933
- if (isThenable(transformer)) {
10934
- return transformer.then((transformer2) => {
10935
- validateTransformer(transformer2);
10936
- return transformer2;
10937
- });
10938
- } else {
10939
- validateTransformer(transformer);
10940
- return transformer;
10941
- }
10942
- } catch (err) {
10943
- if (err instanceof ConfigError) {
10944
- throw new ConfigError(`Failed to load transformer "${name}": ${err.message}`, err);
10945
- } else {
10946
- throw new ConfigError(`Failed to load transformer "${name}"`, ensureError(err));
10947
- }
10948
- }
10949
- }
10950
- function getCachedTransformerFunction(cache, resolvers, name, plugins) {
10951
- const cached = cache.get(name);
10952
- if (cached) {
10953
- return cached;
10954
- } else {
10955
- const transformer = getTransformerFunction(resolvers, name, plugins);
10956
- if (isThenable(transformer)) {
10957
- return transformer.then((transformer2) => {
10958
- cache.set(name, transformer2);
10959
- return transformer2;
10960
- });
10961
- } else {
10962
- cache.set(name, transformer);
10963
- return transformer;
10964
- }
10965
- }
10966
- }
10967
-
10968
- function isIterable(value) {
10969
- return Boolean(value && typeof value === "object" && Symbol.iterator in value);
10970
- }
10971
- function isNonThenableArray(value) {
10972
- return !value.some(isThenable);
10973
- }
10974
- function toArray$1(value) {
10975
- return isIterable(value) ? Array.from(value) : [value];
10976
- }
10977
- const asyncInSyncTransformError = "Cannot use async transformer from sync function";
10978
- class ResolvedConfig {
10979
- metaTable;
10980
- plugins;
10981
- rules;
10982
- transformers;
10983
- cache;
10984
- /** The original data this resolved configuration was created from */
10985
- original;
10986
- /**
10987
- * @internal
10988
- */
10989
- constructor({ metaTable, plugins, rules, transformers }, original) {
10990
- this.metaTable = metaTable;
10991
- this.plugins = plugins;
10992
- this.rules = rules;
10993
- this.transformers = transformers;
10994
- this.cache = /* @__PURE__ */ new Map();
10995
- this.original = original;
10996
- }
10997
- /**
10998
- * Returns the (merged) configuration data used to create this resolved
10999
- * configuration.
11000
- */
11001
- getConfigData() {
11002
- return this.original;
11003
- }
11004
- getMetaTable() {
11005
- return this.metaTable;
11006
- }
11007
- getPlugins() {
11008
- return this.plugins;
11009
- }
11010
- getRules() {
11011
- return this.rules;
11012
- }
11013
- /**
11014
- * Transform a source.
11015
- *
11016
- * When transforming zero or more new sources will be generated.
11017
- *
11018
- * @internal
11019
- * @param source - Current source to transform.
11020
- * @param filename - If set it is the filename used to match
11021
- * transformer. Default is to use filename from source.
11022
- * @returns A list of transformed sources ready for validation.
11023
- */
11024
- async transformSource(resolvers, source, filename) {
11025
- const transformer = this.findTransformer(filename ?? source.filename);
11026
- const context = {
11027
- hasChain: (filename2) => {
11028
- return !!this.findTransformer(filename2);
11029
- },
11030
- chain: (source2, filename2) => {
11031
- return this.transformSource(resolvers, source2, filename2);
11032
- }
11033
- };
11034
- if (!transformer) {
11035
- return Promise.resolve([source]);
11036
- }
11037
- const fn = transformer.kind === "import" ? await getCachedTransformerFunction(this.cache, resolvers, transformer.name, this.plugins) : transformer.function;
11038
- const name = transformer.kind === "import" ? transformer.name : transformer.function.name;
11039
- try {
11040
- const result = await fn.call(context, source);
11041
- const transformedSources = await Promise.all(toArray$1(result));
11042
- for (const source2 of transformedSources) {
11043
- source2.transformedBy ??= [];
11044
- source2.transformedBy.push(name);
11045
- }
11046
- return transformedSources;
11047
- } catch (err) {
11048
- const message = err instanceof Error ? err.message : String(err);
11049
- throw new NestedError(`When transforming "${source.filename}": ${message}`, ensureError(err));
11050
- }
11051
- }
11052
- /**
11053
- * Transform a source.
11054
- *
11055
- * When transforming zero or more new sources will be generated.
11056
- *
11057
- * @internal
11058
- * @param source - Current source to transform.
11059
- * @param filename - If set it is the filename used to match
11060
- * transformer. Default is to use filename from source.
11061
- * @returns A list of transformed sources ready for validation.
11062
- */
11063
- /* eslint-disable-next-line complexity -- there is many ifs'n buts here but
11064
- * hard to break this down without loosing the little clarity that is still
11065
- * left */
11066
- transformSourceSync(resolvers, source, filename) {
11067
- const transformer = this.findTransformer(filename ?? source.filename);
11068
- const context = {
11069
- hasChain: (filename2) => {
11070
- return !!this.findTransformer(filename2);
11071
- },
11072
- chain: (source2, filename2) => {
11073
- return this.transformSourceSync(resolvers, source2, filename2);
11074
- }
11075
- };
11076
- if (!transformer) {
11077
- return [source];
11078
- }
11079
- const fn = transformer.kind === "import" ? getCachedTransformerFunction(this.cache, resolvers, transformer.name, this.plugins) : transformer.function;
11080
- const name = transformer.kind === "import" ? transformer.name : transformer.function.name;
11081
- if (isThenable(fn)) {
11082
- throw new UserError(asyncInSyncTransformError);
11083
- }
11084
- try {
11085
- const result = fn.call(context, source);
11086
- if (isThenable(result)) {
11087
- throw new UserError(asyncInSyncTransformError);
11088
- }
11089
- const transformedSources = toArray$1(result);
11090
- if (!isNonThenableArray(transformedSources)) {
11091
- throw new UserError(asyncInSyncTransformError);
11092
- }
11093
- for (const source2 of transformedSources) {
11094
- source2.transformedBy ??= [];
11095
- source2.transformedBy.push(name);
11096
- }
11097
- return transformedSources;
11098
- } catch (err) {
11099
- const message = err instanceof Error ? err.message : String(err);
11100
- throw new NestedError(`When transforming "${source.filename}": ${message}`, ensureError(err));
11101
- }
11102
- }
11103
- /**
11104
- * Wrapper around [[transformSource]] which reads a file before passing it
11105
- * as-is to transformSource.
11106
- *
11107
- * @internal
11108
- * @param filename - Filename to transform (according to configured
11109
- * transformations)
11110
- * @returns A list of transformed sources ready for validation.
11111
- */
11112
- transformFilename(resolvers, filename) {
11113
- const stdin = 0;
11114
- const src = filename !== "/dev/stdin" ? filename : stdin;
11115
- const data = fs__default.default.readFileSync(src, { encoding: "utf8" });
11116
- const source = {
11117
- data,
11118
- filename,
11119
- line: 1,
11120
- column: 1,
11121
- offset: 0,
11122
- originalData: data
11123
- };
11124
- return this.transformSource(resolvers, source, filename);
11125
- }
11126
- /**
11127
- * Wrapper around [[transformSource]] which reads a file before passing it
11128
- * as-is to transformSource.
11129
- *
11130
- * @internal
11131
- * @param filename - Filename to transform (according to configured
11132
- * transformations)
11133
- * @returns A list of transformed sources ready for validation.
11134
- */
11135
- transformFilenameSync(resolvers, filename) {
11136
- const stdin = 0;
11137
- const src = filename !== "/dev/stdin" ? filename : stdin;
11138
- const data = fs__default.default.readFileSync(src, { encoding: "utf8" });
11139
- const source = {
11140
- data,
11141
- filename,
11142
- line: 1,
11143
- column: 1,
11144
- offset: 0,
11145
- originalData: data
11146
- };
11147
- return this.transformSourceSync(resolvers, source, filename);
11148
- }
11149
- /**
11150
- * Returns true if a transformer matches given filename.
11151
- *
11152
- * @public
11153
- */
11154
- canTransform(filename) {
11155
- const entry = this.findTransformer(filename);
11156
- return !!entry;
11157
- }
11158
- findTransformer(filename) {
11159
- const match = this.transformers.find((entry) => entry.pattern.test(filename));
11160
- return match ?? null;
11161
- }
11162
- }
11163
-
11164
10922
  const ajv = new Ajv__default.default({ strict: true, strictTuples: true, strictTypes: true });
11165
10923
  ajv.addMetaSchema(ajvSchemaDraft);
11166
10924
  ajv.addKeyword(ajvFunctionKeyword);
@@ -11179,7 +10937,7 @@ function mergeInternal(base, rhs) {
11179
10937
  }
11180
10938
  return dst;
11181
10939
  }
11182
- function toArray(value) {
10940
+ function toArray$1(value) {
11183
10941
  if (Array.isArray(value)) {
11184
10942
  return value;
11185
10943
  } else {
@@ -11231,7 +10989,7 @@ class Config {
11231
10989
  * @param filename - The file to read from
11232
10990
  */
11233
10991
  static fromFile(resolvers, filename) {
11234
- const configData = resolveConfig(toArray(resolvers), filename, { cache: false });
10992
+ const configData = resolveConfig(toArray$1(resolvers), filename, { cache: false });
11235
10993
  if (isThenable(configData)) {
11236
10994
  return configData.then((configData2) => Config.fromObject(resolvers, configData2, filename));
11237
10995
  } else {
@@ -11317,7 +11075,7 @@ class Config {
11317
11075
  };
11318
11076
  this.config = mergeInternal(initial, options);
11319
11077
  this.configurations = /* @__PURE__ */ new Map();
11320
- this.resolvers = toArray(resolvers);
11078
+ this.resolvers = toArray$1(resolvers);
11321
11079
  this.metaTable = null;
11322
11080
  this.plugins = [];
11323
11081
  this.transformers = transformerEntries(this.config.transform ?? {});
@@ -12903,43 +12661,251 @@ class StaticConfigLoader extends ConfigLoader {
12903
12661
  return this._resolveConfig(override);
12904
12662
  }
12905
12663
  }
12906
- flushCache() {
12664
+ flushCache() {
12665
+ }
12666
+ defaultConfig() {
12667
+ return this.loadFromObject({
12668
+ extends: ["html-validate:recommended"],
12669
+ elements: ["html5"]
12670
+ });
12671
+ }
12672
+ _resolveConfig(override) {
12673
+ if (override.isRootFound()) {
12674
+ return override.resolve();
12675
+ }
12676
+ const globalConfig = this.getGlobalConfig();
12677
+ if (isThenable(globalConfig)) {
12678
+ return globalConfig.then((globalConfig2) => {
12679
+ const merged = globalConfig2.merge(this.resolvers, override);
12680
+ if (isThenable(merged)) {
12681
+ return merged.then((merged2) => {
12682
+ return merged2.resolve();
12683
+ });
12684
+ } else {
12685
+ return merged.resolve();
12686
+ }
12687
+ });
12688
+ } else {
12689
+ const merged = globalConfig.merge(this.resolvers, override);
12690
+ if (isThenable(merged)) {
12691
+ return merged.then((merged2) => {
12692
+ return merged2.resolve();
12693
+ });
12694
+ } else {
12695
+ return merged.resolve();
12696
+ }
12697
+ }
12698
+ }
12699
+ }
12700
+
12701
+ function getNamedTransformerFromPlugin(name, plugins, pluginName, key) {
12702
+ const plugin = plugins.find((cur) => cur.name === pluginName);
12703
+ if (!plugin) {
12704
+ throw new ConfigError(`No plugin named "${pluginName}" has been loaded`);
12705
+ }
12706
+ if (!plugin.transformer) {
12707
+ throw new ConfigError(`Plugin does not expose any transformers`);
12708
+ }
12709
+ if (typeof plugin.transformer === "function") {
12710
+ throw new ConfigError(
12711
+ `Transformer "${name}" refers to named transformer but plugin exposes only unnamed, use "${pluginName}" instead.`
12712
+ );
12713
+ }
12714
+ const transformer = plugin.transformer[key];
12715
+ if (!transformer) {
12716
+ throw new ConfigError(`Plugin "${pluginName}" does not expose a transformer named "${key}".`);
12717
+ }
12718
+ return transformer;
12719
+ }
12720
+
12721
+ function getTransformerFromModule(resolvers, name) {
12722
+ return resolveTransformer(resolvers, name, { cache: true });
12723
+ }
12724
+
12725
+ function getUnnamedTransformerFromPlugin(name, plugin) {
12726
+ if (!plugin.transformer) {
12727
+ throw new ConfigError(`Plugin does not expose any transformers`);
12728
+ }
12729
+ if (typeof plugin.transformer !== "function") {
12730
+ if (plugin.transformer.default) {
12731
+ return plugin.transformer.default;
12732
+ }
12733
+ throw new ConfigError(
12734
+ `Transformer "${name}" refers to unnamed transformer but plugin exposes only named.`
12735
+ );
12736
+ }
12737
+ return plugin.transformer;
12738
+ }
12739
+
12740
+ const TRANSFORMER_API = {
12741
+ VERSION: 1
12742
+ };
12743
+
12744
+ function validateTransformer(transformer) {
12745
+ const version = transformer.api ?? 0;
12746
+ if (version !== TRANSFORMER_API.VERSION) {
12747
+ throw new ConfigError(
12748
+ `Transformer uses API version ${String(version)} but only version ${String(TRANSFORMER_API.VERSION)} is supported`
12749
+ );
12750
+ }
12751
+ }
12752
+ function loadTransformerFunction(resolvers, name, plugins) {
12753
+ const match = name.match(/(.*):(.*)/);
12754
+ if (match) {
12755
+ const [, pluginName, key] = match;
12756
+ return getNamedTransformerFromPlugin(name, plugins, pluginName, key);
12757
+ }
12758
+ const plugin = plugins.find((cur) => cur.name === name);
12759
+ if (plugin) {
12760
+ return getUnnamedTransformerFromPlugin(name, plugin);
12761
+ }
12762
+ return getTransformerFromModule(resolvers, name);
12763
+ }
12764
+ function getTransformerFunction(resolvers, name, plugins) {
12765
+ try {
12766
+ const transformer = loadTransformerFunction(resolvers, name, plugins);
12767
+ if (isThenable(transformer)) {
12768
+ return transformer.then((transformer2) => {
12769
+ validateTransformer(transformer2);
12770
+ return transformer2;
12771
+ });
12772
+ } else {
12773
+ validateTransformer(transformer);
12774
+ return transformer;
12775
+ }
12776
+ } catch (err) {
12777
+ if (err instanceof ConfigError) {
12778
+ throw new ConfigError(`Failed to load transformer "${name}": ${err.message}`, err);
12779
+ } else {
12780
+ throw new ConfigError(`Failed to load transformer "${name}"`, ensureError(err));
12781
+ }
12782
+ }
12783
+ }
12784
+ function getCachedTransformerFunction(cache, resolvers, name, plugins) {
12785
+ const cached = cache.get(name);
12786
+ if (cached) {
12787
+ return cached;
12788
+ } else {
12789
+ const transformer = getTransformerFunction(resolvers, name, plugins);
12790
+ if (isThenable(transformer)) {
12791
+ return transformer.then((transformer2) => {
12792
+ cache.set(name, transformer2);
12793
+ return transformer2;
12794
+ });
12795
+ } else {
12796
+ cache.set(name, transformer);
12797
+ return transformer;
12798
+ }
12799
+ }
12800
+ }
12801
+
12802
+ function isIterable(value) {
12803
+ return Boolean(value && typeof value === "object" && Symbol.iterator in value);
12804
+ }
12805
+ function toArray(value) {
12806
+ return isIterable(value) ? Array.from(value) : [value];
12807
+ }
12808
+ function isNonThenableArray(value) {
12809
+ return !value.some(isThenable);
12810
+ }
12811
+ const asyncInSyncTransformError = "Cannot use async transformer from sync function";
12812
+ async function transformSource(resolvers, config, source, filename) {
12813
+ const { cache } = config;
12814
+ const transformer = config.findTransformer(filename ?? source.filename);
12815
+ const context = {
12816
+ hasChain(filename2) {
12817
+ return config.canTransform(filename2);
12818
+ },
12819
+ chain(source2, filename2) {
12820
+ return transformSource(resolvers, config, source2, filename2);
12821
+ }
12822
+ };
12823
+ if (!transformer) {
12824
+ return Promise.resolve([source]);
12825
+ }
12826
+ const fn = transformer.kind === "import" ? await getCachedTransformerFunction(cache, resolvers, transformer.name, config.getPlugins()) : transformer.function;
12827
+ const name = transformer.kind === "import" ? transformer.name : transformer.function.name;
12828
+ try {
12829
+ const result = await fn.call(context, source);
12830
+ const transformedSources = await Promise.all(toArray(result));
12831
+ for (const source2 of transformedSources) {
12832
+ source2.transformedBy ??= [];
12833
+ source2.transformedBy.push(name);
12834
+ }
12835
+ return transformedSources;
12836
+ } catch (err) {
12837
+ const message = err instanceof Error ? err.message : String(err);
12838
+ throw new NestedError(`When transforming "${source.filename}": ${message}`, ensureError(err));
12839
+ }
12840
+ }
12841
+ function transformSourceSync(resolvers, config, source, filename) {
12842
+ const { cache } = config;
12843
+ const transformer = config.findTransformer(filename ?? source.filename);
12844
+ const context = {
12845
+ hasChain(filename2) {
12846
+ return config.canTransform(filename2);
12847
+ },
12848
+ chain(source2, filename2) {
12849
+ return transformSourceSync(resolvers, config, source2, filename2);
12850
+ }
12851
+ };
12852
+ if (!transformer) {
12853
+ return [source];
12907
12854
  }
12908
- defaultConfig() {
12909
- return this.loadFromObject({
12910
- extends: ["html-validate:recommended"],
12911
- elements: ["html5"]
12912
- });
12855
+ const fn = transformer.kind === "import" ? getCachedTransformerFunction(cache, resolvers, transformer.name, config.getPlugins()) : transformer.function;
12856
+ const name = transformer.kind === "import" ? transformer.name : transformer.function.name;
12857
+ if (isThenable(fn)) {
12858
+ throw new UserError(asyncInSyncTransformError);
12913
12859
  }
12914
- _resolveConfig(override) {
12915
- if (override.isRootFound()) {
12916
- return override.resolve();
12860
+ try {
12861
+ const result = fn.call(context, source);
12862
+ if (isThenable(result)) {
12863
+ throw new UserError(asyncInSyncTransformError);
12917
12864
  }
12918
- const globalConfig = this.getGlobalConfig();
12919
- if (isThenable(globalConfig)) {
12920
- return globalConfig.then((globalConfig2) => {
12921
- const merged = globalConfig2.merge(this.resolvers, override);
12922
- if (isThenable(merged)) {
12923
- return merged.then((merged2) => {
12924
- return merged2.resolve();
12925
- });
12926
- } else {
12927
- return merged.resolve();
12928
- }
12929
- });
12930
- } else {
12931
- const merged = globalConfig.merge(this.resolvers, override);
12932
- if (isThenable(merged)) {
12933
- return merged.then((merged2) => {
12934
- return merged2.resolve();
12935
- });
12936
- } else {
12937
- return merged.resolve();
12938
- }
12865
+ const transformedSources = toArray(result);
12866
+ if (!isNonThenableArray(transformedSources)) {
12867
+ throw new UserError(asyncInSyncTransformError);
12939
12868
  }
12869
+ for (const source2 of transformedSources) {
12870
+ source2.transformedBy ??= [];
12871
+ source2.transformedBy.push(name);
12872
+ }
12873
+ return transformedSources;
12874
+ } catch (err) {
12875
+ const message = err instanceof Error ? err.message : String(err);
12876
+ throw new NestedError(`When transforming "${source.filename}": ${message}`, ensureError(err));
12940
12877
  }
12941
12878
  }
12942
12879
 
12880
+ function transformFilename(resolvers, config, filename) {
12881
+ const stdin = 0;
12882
+ const src = filename !== "/dev/stdin" ? filename : stdin;
12883
+ const data = fs__default.default.readFileSync(src, { encoding: "utf8" });
12884
+ const source = {
12885
+ data,
12886
+ filename,
12887
+ line: 1,
12888
+ column: 1,
12889
+ offset: 0,
12890
+ originalData: data
12891
+ };
12892
+ return transformSource(resolvers, config, source, filename);
12893
+ }
12894
+ function transformFilenameSync(resolvers, config, filename) {
12895
+ const stdin = 0;
12896
+ const src = filename !== "/dev/stdin" ? filename : stdin;
12897
+ const data = fs__default.default.readFileSync(src, { encoding: "utf8" });
12898
+ const source = {
12899
+ data,
12900
+ filename,
12901
+ line: 1,
12902
+ column: 1,
12903
+ offset: 0,
12904
+ originalData: data
12905
+ };
12906
+ return transformSourceSync(resolvers, config, source, filename);
12907
+ }
12908
+
12943
12909
  function isSourceHooks(value) {
12944
12910
  if (!value || typeof value === "string") {
12945
12911
  return false;
@@ -12999,7 +12965,7 @@ class HtmlValidate {
12999
12965
  const source = normalizeSource(input);
13000
12966
  const config = await this.getConfigFor(source.filename, configOverride);
13001
12967
  const resolvers = this.configLoader.getResolvers();
13002
- const transformedSource = await config.transformSource(resolvers, source);
12968
+ const transformedSource = await transformSource(resolvers, config, source);
13003
12969
  const engine = new Engine(config, Parser);
13004
12970
  return engine.lint(transformedSource);
13005
12971
  }
@@ -13014,7 +12980,7 @@ class HtmlValidate {
13014
12980
  const source = normalizeSource(input);
13015
12981
  const config = this.getConfigForSync(source.filename, configOverride);
13016
12982
  const resolvers = this.configLoader.getResolvers();
13017
- const transformedSource = config.transformSourceSync(resolvers, source);
12983
+ const transformedSource = transformSourceSync(resolvers, config, source);
13018
12984
  const engine = new Engine(config, Parser);
13019
12985
  return engine.lint(transformedSource);
13020
12986
  }
@@ -13028,7 +12994,7 @@ class HtmlValidate {
13028
12994
  async validateFile(filename) {
13029
12995
  const config = await this.getConfigFor(filename);
13030
12996
  const resolvers = this.configLoader.getResolvers();
13031
- const source = await config.transformFilename(resolvers, filename);
12997
+ const source = await transformFilename(resolvers, config, filename);
13032
12998
  const engine = new Engine(config, Parser);
13033
12999
  return Promise.resolve(engine.lint(source));
13034
13000
  }
@@ -13042,7 +13008,7 @@ class HtmlValidate {
13042
13008
  validateFileSync(filename) {
13043
13009
  const config = this.getConfigForSync(filename);
13044
13010
  const resolvers = this.configLoader.getResolvers();
13045
- const source = config.transformFilenameSync(resolvers, filename);
13011
+ const source = transformFilenameSync(resolvers, config, filename);
13046
13012
  const engine = new Engine(config, Parser);
13047
13013
  return engine.lint(source);
13048
13014
  }
@@ -13110,7 +13076,7 @@ class HtmlValidate {
13110
13076
  async dumpTokens(filename) {
13111
13077
  const config = await this.getConfigFor(filename);
13112
13078
  const resolvers = this.configLoader.getResolvers();
13113
- const source = await config.transformFilename(resolvers, filename);
13079
+ const source = await transformFilename(resolvers, config, filename);
13114
13080
  const engine = new Engine(config, Parser);
13115
13081
  return engine.dumpTokens(source);
13116
13082
  }
@@ -13126,7 +13092,7 @@ class HtmlValidate {
13126
13092
  async dumpEvents(filename) {
13127
13093
  const config = await this.getConfigFor(filename);
13128
13094
  const resolvers = this.configLoader.getResolvers();
13129
- const source = await config.transformFilename(resolvers, filename);
13095
+ const source = await transformFilename(resolvers, config, filename);
13130
13096
  const engine = new Engine(config, Parser);
13131
13097
  return engine.dumpEvents(source);
13132
13098
  }
@@ -13142,7 +13108,7 @@ class HtmlValidate {
13142
13108
  async dumpTree(filename) {
13143
13109
  const config = await this.getConfigFor(filename);
13144
13110
  const resolvers = this.configLoader.getResolvers();
13145
- const source = await config.transformFilename(resolvers, filename);
13111
+ const source = await transformFilename(resolvers, config, filename);
13146
13112
  const engine = new Engine(config, Parser);
13147
13113
  return engine.dumpTree(source);
13148
13114
  }
@@ -13158,7 +13124,7 @@ class HtmlValidate {
13158
13124
  async dumpSource(filename) {
13159
13125
  const config = await this.getConfigFor(filename);
13160
13126
  const resolvers = this.configLoader.getResolvers();
13161
- const sources = await config.transformFilename(resolvers, filename);
13127
+ const sources = await transformFilename(resolvers, config, filename);
13162
13128
  return sources.reduce((result, source) => {
13163
13129
  const line = String(source.line);
13164
13130
  const column = String(source.column);
@@ -13357,7 +13323,7 @@ class HtmlValidate {
13357
13323
  }
13358
13324
 
13359
13325
  const name = "html-validate";
13360
- const version = "9.0.1";
13326
+ const version = "9.1.0";
13361
13327
  const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
13362
13328
 
13363
13329
  function definePlugin(plugin) {
@@ -13673,653 +13639,776 @@ function compatibilityCheckImpl(name, declared, options) {
13673
13639
  return false;
13674
13640
  }
13675
13641
 
13676
- var ignore$1;
13677
- var hasRequiredIgnore;
13642
+ // A simple implementation of make-array
13643
+ function makeArray (subject) {
13644
+ return Array.isArray(subject)
13645
+ ? subject
13646
+ : [subject]
13647
+ }
13648
+
13649
+ const UNDEFINED = undefined;
13650
+ const EMPTY = '';
13651
+ const SPACE = ' ';
13652
+ const ESCAPE = '\\';
13653
+ const REGEX_TEST_BLANK_LINE = /^\s+$/;
13654
+ const REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/;
13655
+ const REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/;
13656
+ const REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/;
13657
+ const REGEX_SPLITALL_CRLF = /\r?\n/g;
13658
+
13659
+ // Invalid:
13660
+ // - /foo,
13661
+ // - ./foo,
13662
+ // - ../foo,
13663
+ // - .
13664
+ // - ..
13665
+ // Valid:
13666
+ // - .foo
13667
+ const REGEX_TEST_INVALID_PATH = /^\.*\/|^\.+$/;
13668
+
13669
+ const REGEX_TEST_TRAILING_SLASH = /\/$/;
13670
+
13671
+ const SLASH = '/';
13672
+
13673
+ // Do not use ternary expression here, since "istanbul ignore next" is buggy
13674
+ let TMP_KEY_IGNORE = 'node-ignore';
13675
+ /* istanbul ignore else */
13676
+ if (typeof Symbol !== 'undefined') {
13677
+ TMP_KEY_IGNORE = Symbol.for('node-ignore');
13678
+ }
13679
+ const KEY_IGNORE = TMP_KEY_IGNORE;
13680
+
13681
+ const define = (object, key, value) => {
13682
+ Object.defineProperty(object, key, {value});
13683
+ return value
13684
+ };
13678
13685
 
13679
- function requireIgnore () {
13680
- if (hasRequiredIgnore) return ignore$1;
13681
- hasRequiredIgnore = 1;
13682
- // A simple implementation of make-array
13683
- function makeArray (subject) {
13684
- return Array.isArray(subject)
13685
- ? subject
13686
- : [subject]
13687
- }
13686
+ const REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g;
13687
+
13688
+ const RETURN_FALSE = () => false;
13689
+
13690
+ // Sanitize the range of a regular expression
13691
+ // The cases are complicated, see test cases for details
13692
+ const sanitizeRange = range => range.replace(
13693
+ REGEX_REGEXP_RANGE,
13694
+ (match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0)
13695
+ ? match
13696
+ // Invalid range (out of order) which is ok for gitignore rules but
13697
+ // fatal for JavaScript regular expression, so eliminate it.
13698
+ : EMPTY
13699
+ );
13700
+
13701
+ // See fixtures #59
13702
+ const cleanRangeBackSlash = slashes => {
13703
+ const {length} = slashes;
13704
+ return slashes.slice(0, length - length % 2)
13705
+ };
13688
13706
 
13689
- const EMPTY = '';
13690
- const SPACE = ' ';
13691
- const ESCAPE = '\\';
13692
- const REGEX_TEST_BLANK_LINE = /^\s+$/;
13693
- const REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/;
13694
- const REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/;
13695
- const REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/;
13696
- const REGEX_SPLITALL_CRLF = /\r?\n/g;
13697
- // /foo,
13698
- // ./foo,
13699
- // ../foo,
13700
- // .
13701
- // ..
13702
- const REGEX_TEST_INVALID_PATH = /^\.*\/|^\.+$/;
13703
-
13704
- const SLASH = '/';
13705
-
13706
- // Do not use ternary expression here, since "istanbul ignore next" is buggy
13707
- let TMP_KEY_IGNORE = 'node-ignore';
13708
- /* istanbul ignore else */
13709
- if (typeof Symbol !== 'undefined') {
13710
- TMP_KEY_IGNORE = Symbol.for('node-ignore');
13711
- }
13712
- const KEY_IGNORE = TMP_KEY_IGNORE;
13713
-
13714
- const define = (object, key, value) =>
13715
- Object.defineProperty(object, key, {value});
13716
-
13717
- const REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g;
13718
-
13719
- const RETURN_FALSE = () => false;
13720
-
13721
- // Sanitize the range of a regular expression
13722
- // The cases are complicated, see test cases for details
13723
- const sanitizeRange = range => range.replace(
13724
- REGEX_REGEXP_RANGE,
13725
- (match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0)
13726
- ? match
13727
- // Invalid range (out of order) which is ok for gitignore rules but
13728
- // fatal for JavaScript regular expression, so eliminate it.
13729
- : EMPTY
13730
- );
13731
-
13732
- // See fixtures #59
13733
- const cleanRangeBackSlash = slashes => {
13734
- const {length} = slashes;
13735
- return slashes.slice(0, length - length % 2)
13736
- };
13707
+ // > If the pattern ends with a slash,
13708
+ // > it is removed for the purpose of the following description,
13709
+ // > but it would only find a match with a directory.
13710
+ // > In other words, foo/ will match a directory foo and paths underneath it,
13711
+ // > but will not match a regular file or a symbolic link foo
13712
+ // > (this is consistent with the way how pathspec works in general in Git).
13713
+ // '`foo/`' will not match regular file '`foo`' or symbolic link '`foo`'
13714
+ // -> ignore-rules will not deal with it, because it costs extra `fs.stat` call
13715
+ // you could use option `mark: true` with `glob`
13737
13716
 
13738
- // > If the pattern ends with a slash,
13739
- // > it is removed for the purpose of the following description,
13740
- // > but it would only find a match with a directory.
13741
- // > In other words, foo/ will match a directory foo and paths underneath it,
13742
- // > but will not match a regular file or a symbolic link foo
13743
- // > (this is consistent with the way how pathspec works in general in Git).
13744
- // '`foo/`' will not match regular file '`foo`' or symbolic link '`foo`'
13745
- // -> ignore-rules will not deal with it, because it costs extra `fs.stat` call
13746
- // you could use option `mark: true` with `glob`
13747
-
13748
- // '`foo/`' should not continue with the '`..`'
13749
- const REPLACERS = [
13750
-
13751
- [
13752
- // remove BOM
13753
- // TODO:
13754
- // Other similar zero-width characters?
13755
- /^\uFEFF/,
13756
- () => EMPTY
13757
- ],
13758
-
13759
- // > Trailing spaces are ignored unless they are quoted with backslash ("\")
13760
- [
13761
- // (a\ ) -> (a )
13762
- // (a ) -> (a)
13763
- // (a ) -> (a)
13764
- // (a \ ) -> (a )
13765
- /((?:\\\\)*?)(\\?\s+)$/,
13766
- (_, m1, m2) => m1 + (
13767
- m2.indexOf('\\') === 0
13768
- ? SPACE
13769
- : EMPTY
13770
- )
13771
- ],
13772
-
13773
- // replace (\ ) with ' '
13774
- // (\ ) -> ' '
13775
- // (\\ ) -> '\\ '
13776
- // (\\\ ) -> '\\ '
13777
- [
13778
- /(\\+?)\s/g,
13779
- (_, m1) => {
13780
- const {length} = m1;
13781
- return m1.slice(0, length - length % 2) + SPACE
13782
- }
13783
- ],
13784
-
13785
- // Escape metacharacters
13786
- // which is written down by users but means special for regular expressions.
13787
-
13788
- // > There are 12 characters with special meanings:
13789
- // > - the backslash \,
13790
- // > - the caret ^,
13791
- // > - the dollar sign $,
13792
- // > - the period or dot .,
13793
- // > - the vertical bar or pipe symbol |,
13794
- // > - the question mark ?,
13795
- // > - the asterisk or star *,
13796
- // > - the plus sign +,
13797
- // > - the opening parenthesis (,
13798
- // > - the closing parenthesis ),
13799
- // > - and the opening square bracket [,
13800
- // > - the opening curly brace {,
13801
- // > These special characters are often called "metacharacters".
13802
- [
13803
- /[\\$.|*+(){^]/g,
13804
- match => `\\${match}`
13805
- ],
13806
-
13807
- [
13808
- // > a question mark (?) matches a single character
13809
- /(?!\\)\?/g,
13810
- () => '[^/]'
13811
- ],
13812
-
13813
- // leading slash
13814
- [
13815
-
13816
- // > A leading slash matches the beginning of the pathname.
13817
- // > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".
13818
- // A leading slash matches the beginning of the pathname
13819
- /^\//,
13820
- () => '^'
13821
- ],
13822
-
13823
- // replace special metacharacter slash after the leading slash
13824
- [
13825
- /\//g,
13826
- () => '\\/'
13827
- ],
13828
-
13829
- [
13830
- // > A leading "**" followed by a slash means match in all directories.
13831
- // > For example, "**/foo" matches file or directory "foo" anywhere,
13832
- // > the same as pattern "foo".
13833
- // > "**/foo/bar" matches file or directory "bar" anywhere that is directly
13834
- // > under directory "foo".
13835
- // Notice that the '*'s have been replaced as '\\*'
13836
- /^\^*\\\*\\\*\\\//,
13837
-
13838
- // '**/foo' <-> 'foo'
13839
- () => '^(?:.*\\/)?'
13840
- ],
13841
-
13842
- // starting
13843
- [
13844
- // there will be no leading '/'
13845
- // (which has been replaced by section "leading slash")
13846
- // If starts with '**', adding a '^' to the regular expression also works
13847
- /^(?=[^^])/,
13848
- function startingReplacer () {
13849
- // If has a slash `/` at the beginning or middle
13850
- return !/\/(?!$)/.test(this)
13851
- // > Prior to 2.22.1
13852
- // > If the pattern does not contain a slash /,
13853
- // > Git treats it as a shell glob pattern
13854
- // Actually, if there is only a trailing slash,
13855
- // git also treats it as a shell glob pattern
13856
-
13857
- // After 2.22.1 (compatible but clearer)
13858
- // > If there is a separator at the beginning or middle (or both)
13859
- // > of the pattern, then the pattern is relative to the directory
13860
- // > level of the particular .gitignore file itself.
13861
- // > Otherwise the pattern may also match at any level below
13862
- // > the .gitignore level.
13863
- ? '(?:^|\\/)'
13864
-
13865
- // > Otherwise, Git treats the pattern as a shell glob suitable for
13866
- // > consumption by fnmatch(3)
13867
- : '^'
13868
- }
13869
- ],
13870
-
13871
- // two globstars
13872
- [
13873
- // Use lookahead assertions so that we could match more than one `'/**'`
13874
- /\\\/\\\*\\\*(?=\\\/|$)/g,
13875
-
13876
- // Zero, one or several directories
13877
- // should not use '*', or it will be replaced by the next replacer
13878
-
13879
- // Check if it is not the last `'/**'`
13880
- (_, index, str) => index + 6 < str.length
13881
-
13882
- // case: /**/
13883
- // > A slash followed by two consecutive asterisks then a slash matches
13884
- // > zero or more directories.
13885
- // > For example, "a/**/b" matches "a/b", "a/x/b", "a/x/y/b" and so on.
13886
- // '/**/'
13887
- ? '(?:\\/[^\\/]+)*'
13888
-
13889
- // case: /**
13890
- // > A trailing `"/**"` matches everything inside.
13891
-
13892
- // #21: everything inside but it should not include the current folder
13893
- : '\\/.+'
13894
- ],
13895
-
13896
- // normal intermediate wildcards
13897
- [
13898
- // Never replace escaped '*'
13899
- // ignore rule '\*' will match the path '*'
13900
-
13901
- // 'abc.*/' -> go
13902
- // 'abc.*' -> skip this rule,
13903
- // coz trailing single wildcard will be handed by [trailing wildcard]
13904
- /(^|[^\\]+)(\\\*)+(?=.+)/g,
13905
-
13906
- // '*.js' matches '.js'
13907
- // '*.js' doesn't match 'abc'
13908
- (_, p1, p2) => {
13909
- // 1.
13910
- // > An asterisk "*" matches anything except a slash.
13911
- // 2.
13912
- // > Other consecutive asterisks are considered regular asterisks
13913
- // > and will match according to the previous rules.
13914
- const unescaped = p2.replace(/\\\*/g, '[^\\/]*');
13915
- return p1 + unescaped
13916
- }
13917
- ],
13918
-
13919
- [
13920
- // unescape, revert step 3 except for back slash
13921
- // For example, if a user escape a '\\*',
13922
- // after step 3, the result will be '\\\\\\*'
13923
- /\\\\\\(?=[$.|*+(){^])/g,
13924
- () => ESCAPE
13925
- ],
13926
-
13927
- [
13928
- // '\\\\' -> '\\'
13929
- /\\\\/g,
13930
- () => ESCAPE
13931
- ],
13932
-
13933
- [
13934
- // > The range notation, e.g. [a-zA-Z],
13935
- // > can be used to match one of the characters in a range.
13936
-
13937
- // `\` is escaped by step 3
13938
- /(\\)?\[([^\]/]*?)(\\*)($|\])/g,
13939
- (match, leadEscape, range, endEscape, close) => leadEscape === ESCAPE
13940
- // '\\[bar]' -> '\\\\[bar\\]'
13941
- ? `\\[${range}${cleanRangeBackSlash(endEscape)}${close}`
13942
- : close === ']'
13943
- ? endEscape.length % 2 === 0
13944
- // A normal case, and it is a range notation
13945
- // '[bar]'
13946
- // '[bar\\\\]'
13947
- ? `[${sanitizeRange(range)}${endEscape}]`
13948
- // Invalid range notaton
13949
- // '[bar\\]' -> '[bar\\\\]'
13950
- : '[]'
13951
- : '[]'
13952
- ],
13953
-
13954
- // ending
13955
- [
13956
- // 'js' will not match 'js.'
13957
- // 'ab' will not match 'abc'
13958
- /(?:[^*])$/,
13959
-
13960
- // WTF!
13961
- // https://git-scm.com/docs/gitignore
13962
- // changes in [2.22.1](https://git-scm.com/docs/gitignore/2.22.1)
13963
- // which re-fixes #24, #38
13964
-
13965
- // > If there is a separator at the end of the pattern then the pattern
13966
- // > will only match directories, otherwise the pattern can match both
13967
- // > files and directories.
13968
-
13969
- // 'js*' will not match 'a.js'
13970
- // 'js/' will not match 'a.js'
13971
- // 'js' will match 'a.js' and 'a.js/'
13972
- match => /\/$/.test(match)
13973
- // foo/ will not match 'foo'
13974
- ? `${match}$`
13975
- // foo matches 'foo' and 'foo/'
13976
- : `${match}(?=$|\\/$)`
13977
- ],
13978
-
13979
- // trailing wildcard
13980
- [
13981
- /(\^|\\\/)?\\\*$/,
13982
- (_, p1) => {
13983
- const prefix = p1
13984
- // '\^':
13985
- // '/*' does not match EMPTY
13986
- // '/*' does not match everything
13987
-
13988
- // '\\\/':
13989
- // 'abc/*' does not match 'abc/'
13990
- ? `${p1}[^/]+`
13991
-
13992
- // 'a*' matches 'a'
13993
- // 'a*' matches 'aa'
13994
- : '[^/]*';
13995
-
13996
- return `${prefix}(?=$|\\/$)`
13997
- }
13998
- ],
13999
- ];
14000
-
14001
- // A simple cache, because an ignore rule only has only one certain meaning
14002
- const regexCache = Object.create(null);
14003
-
14004
- // @param {pattern}
14005
- const makeRegex = (pattern, ignoreCase) => {
14006
- let source = regexCache[pattern];
14007
-
14008
- if (!source) {
14009
- source = REPLACERS.reduce(
14010
- (prev, [matcher, replacer]) =>
14011
- prev.replace(matcher, replacer.bind(pattern)),
14012
- pattern
14013
- );
14014
- regexCache[pattern] = source;
14015
- }
14016
-
14017
- return ignoreCase
14018
- ? new RegExp(source, 'i')
14019
- : new RegExp(source)
14020
- };
13717
+ // '`foo/`' should not continue with the '`..`'
13718
+ const REPLACERS = [
14021
13719
 
14022
- const isString = subject => typeof subject === 'string';
14023
-
14024
- // > A blank line matches no files, so it can serve as a separator for readability.
14025
- const checkPattern = pattern => pattern
14026
- && isString(pattern)
14027
- && !REGEX_TEST_BLANK_LINE.test(pattern)
14028
- && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern)
14029
-
14030
- // > A line starting with # serves as a comment.
14031
- && pattern.indexOf('#') !== 0;
14032
-
14033
- const splitPattern = pattern => pattern.split(REGEX_SPLITALL_CRLF);
14034
-
14035
- class IgnoreRule {
14036
- constructor (
14037
- origin,
14038
- pattern,
14039
- negative,
14040
- regex
14041
- ) {
14042
- this.origin = origin;
14043
- this.pattern = pattern;
14044
- this.negative = negative;
14045
- this.regex = regex;
14046
- }
14047
- }
13720
+ [
13721
+ // Remove BOM
13722
+ // TODO:
13723
+ // Other similar zero-width characters?
13724
+ /^\uFEFF/,
13725
+ () => EMPTY
13726
+ ],
14048
13727
 
14049
- const createRule = (pattern, ignoreCase) => {
14050
- const origin = pattern;
14051
- let negative = false;
14052
-
14053
- // > An optional prefix "!" which negates the pattern;
14054
- if (pattern.indexOf('!') === 0) {
14055
- negative = true;
14056
- pattern = pattern.substr(1);
14057
- }
14058
-
14059
- pattern = pattern
14060
- // > Put a backslash ("\") in front of the first "!" for patterns that
14061
- // > begin with a literal "!", for example, `"\!important!.txt"`.
14062
- .replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, '!')
14063
- // > Put a backslash ("\") in front of the first hash for patterns that
14064
- // > begin with a hash.
14065
- .replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, '#');
14066
-
14067
- const regex = makeRegex(pattern, ignoreCase);
14068
-
14069
- return new IgnoreRule(
14070
- origin,
14071
- pattern,
14072
- negative,
14073
- regex
14074
- )
14075
- };
13728
+ // > Trailing spaces are ignored unless they are quoted with backslash ("\")
13729
+ [
13730
+ // (a\ ) -> (a )
13731
+ // (a ) -> (a)
13732
+ // (a ) -> (a)
13733
+ // (a \ ) -> (a )
13734
+ /((?:\\\\)*?)(\\?\s+)$/,
13735
+ (_, m1, m2) => m1 + (
13736
+ m2.indexOf('\\') === 0
13737
+ ? SPACE
13738
+ : EMPTY
13739
+ )
13740
+ ],
14076
13741
 
14077
- const throwError = (message, Ctor) => {
14078
- throw new Ctor(message)
14079
- };
13742
+ // Replace (\ ) with ' '
13743
+ // (\ ) -> ' '
13744
+ // (\\ ) -> '\\ '
13745
+ // (\\\ ) -> '\\ '
13746
+ [
13747
+ /(\\+?)\s/g,
13748
+ (_, m1) => {
13749
+ const {length} = m1;
13750
+ return m1.slice(0, length - length % 2) + SPACE
13751
+ }
13752
+ ],
14080
13753
 
14081
- const checkPath = (path, originalPath, doThrow) => {
14082
- if (!isString(path)) {
14083
- return doThrow(
14084
- `path must be a string, but got \`${originalPath}\``,
14085
- TypeError
14086
- )
14087
- }
14088
-
14089
- // We don't know if we should ignore EMPTY, so throw
14090
- if (!path) {
14091
- return doThrow(`path must not be empty`, TypeError)
14092
- }
14093
-
14094
- // Check if it is a relative path
14095
- if (checkPath.isNotRelative(path)) {
14096
- const r = '`path.relative()`d';
14097
- return doThrow(
14098
- `path should be a ${r} string, but got "${originalPath}"`,
14099
- RangeError
14100
- )
14101
- }
14102
-
14103
- return true
14104
- };
13754
+ // Escape metacharacters
13755
+ // which is written down by users but means special for regular expressions.
13756
+
13757
+ // > There are 12 characters with special meanings:
13758
+ // > - the backslash \,
13759
+ // > - the caret ^,
13760
+ // > - the dollar sign $,
13761
+ // > - the period or dot .,
13762
+ // > - the vertical bar or pipe symbol |,
13763
+ // > - the question mark ?,
13764
+ // > - the asterisk or star *,
13765
+ // > - the plus sign +,
13766
+ // > - the opening parenthesis (,
13767
+ // > - the closing parenthesis ),
13768
+ // > - and the opening square bracket [,
13769
+ // > - the opening curly brace {,
13770
+ // > These special characters are often called "metacharacters".
13771
+ [
13772
+ /[\\$.|*+(){^]/g,
13773
+ match => `\\${match}`
13774
+ ],
14105
13775
 
14106
- const isNotRelative = path => REGEX_TEST_INVALID_PATH.test(path);
14107
-
14108
- checkPath.isNotRelative = isNotRelative;
14109
- checkPath.convert = p => p;
14110
-
14111
- class Ignore {
14112
- constructor ({
14113
- ignorecase = true,
14114
- ignoreCase = ignorecase,
14115
- allowRelativePaths = false
14116
- } = {}) {
14117
- define(this, KEY_IGNORE, true);
14118
-
14119
- this._rules = [];
14120
- this._ignoreCase = ignoreCase;
14121
- this._allowRelativePaths = allowRelativePaths;
14122
- this._initCache();
14123
- }
14124
-
14125
- _initCache () {
14126
- this._ignoreCache = Object.create(null);
14127
- this._testCache = Object.create(null);
14128
- }
14129
-
14130
- _addPattern (pattern) {
14131
- // #32
14132
- if (pattern && pattern[KEY_IGNORE]) {
14133
- this._rules = this._rules.concat(pattern._rules);
14134
- this._added = true;
14135
- return
14136
- }
14137
-
14138
- if (checkPattern(pattern)) {
14139
- const rule = createRule(pattern, this._ignoreCase);
14140
- this._added = true;
14141
- this._rules.push(rule);
14142
- }
14143
- }
14144
-
14145
- // @param {Array<string> | string | Ignore} pattern
14146
- add (pattern) {
14147
- this._added = false;
14148
-
14149
- makeArray(
14150
- isString(pattern)
14151
- ? splitPattern(pattern)
14152
- : pattern
14153
- ).forEach(this._addPattern, this);
14154
-
14155
- // Some rules have just added to the ignore,
14156
- // making the behavior changed.
14157
- if (this._added) {
14158
- this._initCache();
14159
- }
14160
-
14161
- return this
14162
- }
14163
-
14164
- // legacy
14165
- addPattern (pattern) {
14166
- return this.add(pattern)
14167
- }
14168
-
14169
- // | ignored : unignored
14170
- // negative | 0:0 | 0:1 | 1:0 | 1:1
14171
- // -------- | ------- | ------- | ------- | --------
14172
- // 0 | TEST | TEST | SKIP | X
14173
- // 1 | TESTIF | SKIP | TEST | X
14174
-
14175
- // - SKIP: always skip
14176
- // - TEST: always test
14177
- // - TESTIF: only test if checkUnignored
14178
- // - X: that never happen
14179
-
14180
- // @param {boolean} whether should check if the path is unignored,
14181
- // setting `checkUnignored` to `false` could reduce additional
14182
- // path matching.
14183
-
14184
- // @returns {TestResult} true if a file is ignored
14185
- _testOne (path, checkUnignored) {
14186
- let ignored = false;
14187
- let unignored = false;
14188
-
14189
- this._rules.forEach(rule => {
14190
- const {negative} = rule;
14191
- if (
14192
- unignored === negative && ignored !== unignored
14193
- || negative && !ignored && !unignored && !checkUnignored
14194
- ) {
14195
- return
14196
- }
14197
-
14198
- const matched = rule.regex.test(path);
14199
-
14200
- if (matched) {
14201
- ignored = !negative;
14202
- unignored = negative;
14203
- }
14204
- });
14205
-
14206
- return {
14207
- ignored,
14208
- unignored
14209
- }
14210
- }
14211
-
14212
- // @returns {TestResult}
14213
- _test (originalPath, cache, checkUnignored, slices) {
14214
- const path = originalPath
14215
- // Supports nullable path
14216
- && checkPath.convert(originalPath);
14217
-
14218
- checkPath(
14219
- path,
14220
- originalPath,
14221
- this._allowRelativePaths
14222
- ? RETURN_FALSE
14223
- : throwError
14224
- );
14225
-
14226
- return this._t(path, cache, checkUnignored, slices)
14227
- }
14228
-
14229
- _t (path, cache, checkUnignored, slices) {
14230
- if (path in cache) {
14231
- return cache[path]
14232
- }
14233
-
14234
- if (!slices) {
14235
- // path/to/a.js
14236
- // ['path', 'to', 'a.js']
14237
- slices = path.split(SLASH);
14238
- }
14239
-
14240
- slices.pop();
14241
-
14242
- // If the path has no parent directory, just test it
14243
- if (!slices.length) {
14244
- return cache[path] = this._testOne(path, checkUnignored)
14245
- }
14246
-
14247
- const parent = this._t(
14248
- slices.join(SLASH) + SLASH,
14249
- cache,
14250
- checkUnignored,
14251
- slices
14252
- );
14253
-
14254
- // If the path contains a parent directory, check the parent first
14255
- return cache[path] = parent.ignored
14256
- // > It is not possible to re-include a file if a parent directory of
14257
- // > that file is excluded.
14258
- ? parent
14259
- : this._testOne(path, checkUnignored)
14260
- }
14261
-
14262
- ignores (path) {
14263
- return this._test(path, this._ignoreCache, false).ignored
14264
- }
14265
-
14266
- createFilter () {
14267
- return path => !this.ignores(path)
14268
- }
14269
-
14270
- filter (paths) {
14271
- return makeArray(paths).filter(this.createFilter())
14272
- }
14273
-
14274
- // @returns {TestResult}
14275
- test (path) {
14276
- return this._test(path, this._testCache, true)
14277
- }
14278
- }
13776
+ [
13777
+ // > a question mark (?) matches a single character
13778
+ /(?!\\)\?/g,
13779
+ () => '[^/]'
13780
+ ],
14279
13781
 
14280
- const factory = options => new Ignore(options);
14281
-
14282
- const isPathValid = path =>
14283
- checkPath(path && checkPath.convert(path), path, RETURN_FALSE);
14284
-
14285
- factory.isPathValid = isPathValid;
14286
-
14287
- // Fixes typescript
14288
- factory.default = factory;
14289
-
14290
- ignore$1 = factory;
14291
-
14292
- // Windows
14293
- // --------------------------------------------------------------
14294
- /* istanbul ignore if */
14295
- if (
14296
- // Detect `process` so that it can run in browsers.
14297
- typeof process !== 'undefined'
14298
- && (
14299
- process.env && process.env.IGNORE_TEST_WIN32
14300
- || process.platform === 'win32'
14301
- )
14302
- ) {
14303
- /* eslint no-control-regex: "off" */
14304
- const makePosix = str => /^\\\\\?\\/.test(str)
14305
- || /["<>|\u0000-\u001F]+/u.test(str)
14306
- ? str
14307
- : str.replace(/\\/g, '/');
14308
-
14309
- checkPath.convert = makePosix;
14310
-
14311
- // 'C:\\foo' <- 'C:\\foo' has been converted to 'C:/'
14312
- // 'd:\\foo'
14313
- const REGIX_IS_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
14314
- checkPath.isNotRelative = path =>
14315
- REGIX_IS_WINDOWS_PATH_ABSOLUTE.test(path)
14316
- || isNotRelative(path);
14317
- }
14318
- return ignore$1;
13782
+ // leading slash
13783
+ [
13784
+
13785
+ // > A leading slash matches the beginning of the pathname.
13786
+ // > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".
13787
+ // A leading slash matches the beginning of the pathname
13788
+ /^\//,
13789
+ () => '^'
13790
+ ],
13791
+
13792
+ // replace special metacharacter slash after the leading slash
13793
+ [
13794
+ /\//g,
13795
+ () => '\\/'
13796
+ ],
13797
+
13798
+ [
13799
+ // > A leading "**" followed by a slash means match in all directories.
13800
+ // > For example, "**/foo" matches file or directory "foo" anywhere,
13801
+ // > the same as pattern "foo".
13802
+ // > "**/foo/bar" matches file or directory "bar" anywhere that is directly
13803
+ // > under directory "foo".
13804
+ // Notice that the '*'s have been replaced as '\\*'
13805
+ /^\^*\\\*\\\*\\\//,
13806
+
13807
+ // '**/foo' <-> 'foo'
13808
+ () => '^(?:.*\\/)?'
13809
+ ],
13810
+
13811
+ // starting
13812
+ [
13813
+ // there will be no leading '/'
13814
+ // (which has been replaced by section "leading slash")
13815
+ // If starts with '**', adding a '^' to the regular expression also works
13816
+ /^(?=[^^])/,
13817
+ function startingReplacer () {
13818
+ // If has a slash `/` at the beginning or middle
13819
+ return !/\/(?!$)/.test(this)
13820
+ // > Prior to 2.22.1
13821
+ // > If the pattern does not contain a slash /,
13822
+ // > Git treats it as a shell glob pattern
13823
+ // Actually, if there is only a trailing slash,
13824
+ // git also treats it as a shell glob pattern
13825
+
13826
+ // After 2.22.1 (compatible but clearer)
13827
+ // > If there is a separator at the beginning or middle (or both)
13828
+ // > of the pattern, then the pattern is relative to the directory
13829
+ // > level of the particular .gitignore file itself.
13830
+ // > Otherwise the pattern may also match at any level below
13831
+ // > the .gitignore level.
13832
+ ? '(?:^|\\/)'
13833
+
13834
+ // > Otherwise, Git treats the pattern as a shell glob suitable for
13835
+ // > consumption by fnmatch(3)
13836
+ : '^'
13837
+ }
13838
+ ],
13839
+
13840
+ // two globstars
13841
+ [
13842
+ // Use lookahead assertions so that we could match more than one `'/**'`
13843
+ /\\\/\\\*\\\*(?=\\\/|$)/g,
13844
+
13845
+ // Zero, one or several directories
13846
+ // should not use '*', or it will be replaced by the next replacer
13847
+
13848
+ // Check if it is not the last `'/**'`
13849
+ (_, index, str) => index + 6 < str.length
13850
+
13851
+ // case: /**/
13852
+ // > A slash followed by two consecutive asterisks then a slash matches
13853
+ // > zero or more directories.
13854
+ // > For example, "a/**/b" matches "a/b", "a/x/b", "a/x/y/b" and so on.
13855
+ // '/**/'
13856
+ ? '(?:\\/[^\\/]+)*'
13857
+
13858
+ // case: /**
13859
+ // > A trailing `"/**"` matches everything inside.
13860
+
13861
+ // #21: everything inside but it should not include the current folder
13862
+ : '\\/.+'
13863
+ ],
13864
+
13865
+ // normal intermediate wildcards
13866
+ [
13867
+ // Never replace escaped '*'
13868
+ // ignore rule '\*' will match the path '*'
13869
+
13870
+ // 'abc.*/' -> go
13871
+ // 'abc.*' -> skip this rule,
13872
+ // coz trailing single wildcard will be handed by [trailing wildcard]
13873
+ /(^|[^\\]+)(\\\*)+(?=.+)/g,
13874
+
13875
+ // '*.js' matches '.js'
13876
+ // '*.js' doesn't match 'abc'
13877
+ (_, p1, p2) => {
13878
+ // 1.
13879
+ // > An asterisk "*" matches anything except a slash.
13880
+ // 2.
13881
+ // > Other consecutive asterisks are considered regular asterisks
13882
+ // > and will match according to the previous rules.
13883
+ const unescaped = p2.replace(/\\\*/g, '[^\\/]*');
13884
+ return p1 + unescaped
13885
+ }
13886
+ ],
13887
+
13888
+ [
13889
+ // unescape, revert step 3 except for back slash
13890
+ // For example, if a user escape a '\\*',
13891
+ // after step 3, the result will be '\\\\\\*'
13892
+ /\\\\\\(?=[$.|*+(){^])/g,
13893
+ () => ESCAPE
13894
+ ],
13895
+
13896
+ [
13897
+ // '\\\\' -> '\\'
13898
+ /\\\\/g,
13899
+ () => ESCAPE
13900
+ ],
13901
+
13902
+ [
13903
+ // > The range notation, e.g. [a-zA-Z],
13904
+ // > can be used to match one of the characters in a range.
13905
+
13906
+ // `\` is escaped by step 3
13907
+ /(\\)?\[([^\]/]*?)(\\*)($|\])/g,
13908
+ (match, leadEscape, range, endEscape, close) => leadEscape === ESCAPE
13909
+ // '\\[bar]' -> '\\\\[bar\\]'
13910
+ ? `\\[${range}${cleanRangeBackSlash(endEscape)}${close}`
13911
+ : close === ']'
13912
+ ? endEscape.length % 2 === 0
13913
+ // A normal case, and it is a range notation
13914
+ // '[bar]'
13915
+ // '[bar\\\\]'
13916
+ ? `[${sanitizeRange(range)}${endEscape}]`
13917
+ // Invalid range notaton
13918
+ // '[bar\\]' -> '[bar\\\\]'
13919
+ : '[]'
13920
+ : '[]'
13921
+ ],
13922
+
13923
+ // ending
13924
+ [
13925
+ // 'js' will not match 'js.'
13926
+ // 'ab' will not match 'abc'
13927
+ /(?:[^*])$/,
13928
+
13929
+ // WTF!
13930
+ // https://git-scm.com/docs/gitignore
13931
+ // changes in [2.22.1](https://git-scm.com/docs/gitignore/2.22.1)
13932
+ // which re-fixes #24, #38
13933
+
13934
+ // > If there is a separator at the end of the pattern then the pattern
13935
+ // > will only match directories, otherwise the pattern can match both
13936
+ // > files and directories.
13937
+
13938
+ // 'js*' will not match 'a.js'
13939
+ // 'js/' will not match 'a.js'
13940
+ // 'js' will match 'a.js' and 'a.js/'
13941
+ match => /\/$/.test(match)
13942
+ // foo/ will not match 'foo'
13943
+ ? `${match}$`
13944
+ // foo matches 'foo' and 'foo/'
13945
+ : `${match}(?=$|\\/$)`
13946
+ ]
13947
+ ];
13948
+
13949
+ const REGEX_REPLACE_TRAILING_WILDCARD = /(^|\\\/)?\\\*$/;
13950
+ const MODE_IGNORE = 'regex';
13951
+ const MODE_CHECK_IGNORE = 'checkRegex';
13952
+ const UNDERSCORE = '_';
13953
+
13954
+ const TRAILING_WILD_CARD_REPLACERS = {
13955
+ [MODE_IGNORE] (_, p1) {
13956
+ const prefix = p1
13957
+ // '\^':
13958
+ // '/*' does not match EMPTY
13959
+ // '/*' does not match everything
13960
+
13961
+ // '\\\/':
13962
+ // 'abc/*' does not match 'abc/'
13963
+ ? `${p1}[^/]+`
13964
+
13965
+ // 'a*' matches 'a'
13966
+ // 'a*' matches 'aa'
13967
+ : '[^/]*';
13968
+
13969
+ return `${prefix}(?=$|\\/$)`
13970
+ },
13971
+
13972
+ [MODE_CHECK_IGNORE] (_, p1) {
13973
+ // When doing `git check-ignore`
13974
+ const prefix = p1
13975
+ // '\\\/':
13976
+ // 'abc/*' DOES match 'abc/' !
13977
+ ? `${p1}[^/]*`
13978
+
13979
+ // 'a*' matches 'a'
13980
+ // 'a*' matches 'aa'
13981
+ : '[^/]*';
13982
+
13983
+ return `${prefix}(?=$|\\/$)`
13984
+ }
13985
+ };
13986
+
13987
+ // @param {pattern}
13988
+ const makeRegexPrefix = pattern => REPLACERS.reduce(
13989
+ (prev, [matcher, replacer]) =>
13990
+ prev.replace(matcher, replacer.bind(pattern)),
13991
+ pattern
13992
+ );
13993
+
13994
+ const isString = subject => typeof subject === 'string';
13995
+
13996
+ // > A blank line matches no files, so it can serve as a separator for readability.
13997
+ const checkPattern = pattern => pattern
13998
+ && isString(pattern)
13999
+ && !REGEX_TEST_BLANK_LINE.test(pattern)
14000
+ && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern)
14001
+
14002
+ // > A line starting with # serves as a comment.
14003
+ && pattern.indexOf('#') !== 0;
14004
+
14005
+ const splitPattern = pattern => pattern
14006
+ .split(REGEX_SPLITALL_CRLF)
14007
+ .filter(Boolean);
14008
+
14009
+ class IgnoreRule {
14010
+ constructor (
14011
+ pattern,
14012
+ mark,
14013
+ body,
14014
+ ignoreCase,
14015
+ negative,
14016
+ prefix
14017
+ ) {
14018
+ this.pattern = pattern;
14019
+ this.mark = mark;
14020
+ this.negative = negative;
14021
+
14022
+ define(this, 'body', body);
14023
+ define(this, 'ignoreCase', ignoreCase);
14024
+ define(this, 'regexPrefix', prefix);
14025
+ }
14026
+
14027
+ get regex () {
14028
+ const key = UNDERSCORE + MODE_IGNORE;
14029
+
14030
+ if (this[key]) {
14031
+ return this[key]
14032
+ }
14033
+
14034
+ return this._make(MODE_IGNORE, key)
14035
+ }
14036
+
14037
+ get checkRegex () {
14038
+ const key = UNDERSCORE + MODE_CHECK_IGNORE;
14039
+
14040
+ if (this[key]) {
14041
+ return this[key]
14042
+ }
14043
+
14044
+ return this._make(MODE_CHECK_IGNORE, key)
14045
+ }
14046
+
14047
+ _make (mode, key) {
14048
+ const str = this.regexPrefix.replace(
14049
+ REGEX_REPLACE_TRAILING_WILDCARD,
14050
+
14051
+ // It does not need to bind pattern
14052
+ TRAILING_WILD_CARD_REPLACERS[mode]
14053
+ );
14054
+
14055
+ const regex = this.ignoreCase
14056
+ ? new RegExp(str, 'i')
14057
+ : new RegExp(str);
14058
+
14059
+ return define(this, key, regex)
14060
+ }
14061
+ }
14062
+
14063
+ const createRule = ({
14064
+ pattern,
14065
+ mark
14066
+ }, ignoreCase) => {
14067
+ let negative = false;
14068
+ let body = pattern;
14069
+
14070
+ // > An optional prefix "!" which negates the pattern;
14071
+ if (body.indexOf('!') === 0) {
14072
+ negative = true;
14073
+ body = body.substr(1);
14074
+ }
14075
+
14076
+ body = body
14077
+ // > Put a backslash ("\") in front of the first "!" for patterns that
14078
+ // > begin with a literal "!", for example, `"\!important!.txt"`.
14079
+ .replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, '!')
14080
+ // > Put a backslash ("\") in front of the first hash for patterns that
14081
+ // > begin with a hash.
14082
+ .replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, '#');
14083
+
14084
+ const regexPrefix = makeRegexPrefix(body);
14085
+
14086
+ return new IgnoreRule(
14087
+ pattern,
14088
+ mark,
14089
+ body,
14090
+ ignoreCase,
14091
+ negative,
14092
+ regexPrefix
14093
+ )
14094
+ };
14095
+
14096
+ class RuleManager {
14097
+ constructor (ignoreCase) {
14098
+ this._ignoreCase = ignoreCase;
14099
+ this._rules = [];
14100
+ }
14101
+
14102
+ _add (pattern) {
14103
+ // #32
14104
+ if (pattern && pattern[KEY_IGNORE]) {
14105
+ this._rules = this._rules.concat(pattern._rules._rules);
14106
+ this._added = true;
14107
+ return
14108
+ }
14109
+
14110
+ if (isString(pattern)) {
14111
+ pattern = {
14112
+ pattern
14113
+ };
14114
+ }
14115
+
14116
+ if (checkPattern(pattern.pattern)) {
14117
+ const rule = createRule(pattern, this._ignoreCase);
14118
+ this._added = true;
14119
+ this._rules.push(rule);
14120
+ }
14121
+ }
14122
+
14123
+ // @param {Array<string> | string | Ignore} pattern
14124
+ add (pattern) {
14125
+ this._added = false;
14126
+
14127
+ makeArray(
14128
+ isString(pattern)
14129
+ ? splitPattern(pattern)
14130
+ : pattern
14131
+ ).forEach(this._add, this);
14132
+
14133
+ return this._added
14134
+ }
14135
+
14136
+ // Test one single path without recursively checking parent directories
14137
+ //
14138
+ // - checkUnignored `boolean` whether should check if the path is unignored,
14139
+ // setting `checkUnignored` to `false` could reduce additional
14140
+ // path matching.
14141
+ // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
14142
+
14143
+ // @returns {TestResult} true if a file is ignored
14144
+ test (path, checkUnignored, mode) {
14145
+ let ignored = false;
14146
+ let unignored = false;
14147
+ let matchedRule;
14148
+
14149
+ this._rules.forEach(rule => {
14150
+ const {negative} = rule;
14151
+
14152
+ // | ignored : unignored
14153
+ // -------- | ---------------------------------------
14154
+ // negative | 0:0 | 0:1 | 1:0 | 1:1
14155
+ // -------- | ------- | ------- | ------- | --------
14156
+ // 0 | TEST | TEST | SKIP | X
14157
+ // 1 | TESTIF | SKIP | TEST | X
14158
+
14159
+ // - SKIP: always skip
14160
+ // - TEST: always test
14161
+ // - TESTIF: only test if checkUnignored
14162
+ // - X: that never happen
14163
+ if (
14164
+ unignored === negative && ignored !== unignored
14165
+ || negative && !ignored && !unignored && !checkUnignored
14166
+ ) {
14167
+ return
14168
+ }
14169
+
14170
+ const matched = rule[mode].test(path);
14171
+
14172
+ if (!matched) {
14173
+ return
14174
+ }
14175
+
14176
+ ignored = !negative;
14177
+ unignored = negative;
14178
+
14179
+ matchedRule = negative
14180
+ ? UNDEFINED
14181
+ : rule;
14182
+ });
14183
+
14184
+ const ret = {
14185
+ ignored,
14186
+ unignored
14187
+ };
14188
+
14189
+ if (matchedRule) {
14190
+ ret.rule = matchedRule;
14191
+ }
14192
+
14193
+ return ret
14194
+ }
14195
+ }
14196
+
14197
+ const throwError = (message, Ctor) => {
14198
+ throw new Ctor(message)
14199
+ };
14200
+
14201
+ const checkPath = (path, originalPath, doThrow) => {
14202
+ if (!isString(path)) {
14203
+ return doThrow(
14204
+ `path must be a string, but got \`${originalPath}\``,
14205
+ TypeError
14206
+ )
14207
+ }
14208
+
14209
+ // We don't know if we should ignore EMPTY, so throw
14210
+ if (!path) {
14211
+ return doThrow(`path must not be empty`, TypeError)
14212
+ }
14213
+
14214
+ // Check if it is a relative path
14215
+ if (checkPath.isNotRelative(path)) {
14216
+ const r = '`path.relative()`d';
14217
+ return doThrow(
14218
+ `path should be a ${r} string, but got "${originalPath}"`,
14219
+ RangeError
14220
+ )
14221
+ }
14222
+
14223
+ return true
14224
+ };
14225
+
14226
+ const isNotRelative = path => REGEX_TEST_INVALID_PATH.test(path);
14227
+
14228
+ checkPath.isNotRelative = isNotRelative;
14229
+
14230
+ // On windows, the following function will be replaced
14231
+ /* istanbul ignore next */
14232
+ checkPath.convert = p => p;
14233
+
14234
+
14235
+ class Ignore {
14236
+ constructor ({
14237
+ ignorecase = true,
14238
+ ignoreCase = ignorecase,
14239
+ allowRelativePaths = false
14240
+ } = {}) {
14241
+ define(this, KEY_IGNORE, true);
14242
+
14243
+ this._rules = new RuleManager(ignoreCase);
14244
+ this._strictPathCheck = !allowRelativePaths;
14245
+ this._initCache();
14246
+ }
14247
+
14248
+ _initCache () {
14249
+ // A cache for the result of `.ignores()`
14250
+ this._ignoreCache = Object.create(null);
14251
+
14252
+ // A cache for the result of `.test()`
14253
+ this._testCache = Object.create(null);
14254
+ }
14255
+
14256
+ add (pattern) {
14257
+ if (this._rules.add(pattern)) {
14258
+ // Some rules have just added to the ignore,
14259
+ // making the behavior changed,
14260
+ // so we need to re-initialize the result cache
14261
+ this._initCache();
14262
+ }
14263
+
14264
+ return this
14265
+ }
14266
+
14267
+ // legacy
14268
+ addPattern (pattern) {
14269
+ return this.add(pattern)
14270
+ }
14271
+
14272
+ // @returns {TestResult}
14273
+ _test (originalPath, cache, checkUnignored, slices) {
14274
+ const path = originalPath
14275
+ // Supports nullable path
14276
+ && checkPath.convert(originalPath);
14277
+
14278
+ checkPath(
14279
+ path,
14280
+ originalPath,
14281
+ this._strictPathCheck
14282
+ ? throwError
14283
+ : RETURN_FALSE
14284
+ );
14285
+
14286
+ return this._t(path, cache, checkUnignored, slices)
14287
+ }
14288
+
14289
+ checkIgnore (path) {
14290
+ // If the path doest not end with a slash, `.ignores()` is much equivalent
14291
+ // to `git check-ignore`
14292
+ if (!REGEX_TEST_TRAILING_SLASH.test(path)) {
14293
+ return this.test(path)
14294
+ }
14295
+
14296
+ const slices = path.split(SLASH).filter(Boolean);
14297
+ slices.pop();
14298
+
14299
+ if (slices.length) {
14300
+ const parent = this._t(
14301
+ slices.join(SLASH) + SLASH,
14302
+ this._testCache,
14303
+ true,
14304
+ slices
14305
+ );
14306
+
14307
+ if (parent.ignored) {
14308
+ return parent
14309
+ }
14310
+ }
14311
+
14312
+ return this._rules.test(path, false, MODE_CHECK_IGNORE)
14313
+ }
14314
+
14315
+ _t (
14316
+ // The path to be tested
14317
+ path,
14318
+
14319
+ // The cache for the result of a certain checking
14320
+ cache,
14321
+
14322
+ // Whether should check if the path is unignored
14323
+ checkUnignored,
14324
+
14325
+ // The path slices
14326
+ slices
14327
+ ) {
14328
+ if (path in cache) {
14329
+ return cache[path]
14330
+ }
14331
+
14332
+ if (!slices) {
14333
+ // path/to/a.js
14334
+ // ['path', 'to', 'a.js']
14335
+ slices = path.split(SLASH).filter(Boolean);
14336
+ }
14337
+
14338
+ slices.pop();
14339
+
14340
+ // If the path has no parent directory, just test it
14341
+ if (!slices.length) {
14342
+ return cache[path] = this._rules.test(path, checkUnignored, MODE_IGNORE)
14343
+ }
14344
+
14345
+ const parent = this._t(
14346
+ slices.join(SLASH) + SLASH,
14347
+ cache,
14348
+ checkUnignored,
14349
+ slices
14350
+ );
14351
+
14352
+ // If the path contains a parent directory, check the parent first
14353
+ return cache[path] = parent.ignored
14354
+ // > It is not possible to re-include a file if a parent directory of
14355
+ // > that file is excluded.
14356
+ ? parent
14357
+ : this._rules.test(path, checkUnignored, MODE_IGNORE)
14358
+ }
14359
+
14360
+ ignores (path) {
14361
+ return this._test(path, this._ignoreCache, false).ignored
14362
+ }
14363
+
14364
+ createFilter () {
14365
+ return path => !this.ignores(path)
14366
+ }
14367
+
14368
+ filter (paths) {
14369
+ return makeArray(paths).filter(this.createFilter())
14370
+ }
14371
+
14372
+ // @returns {TestResult}
14373
+ test (path) {
14374
+ return this._test(path, this._testCache, true)
14375
+ }
14319
14376
  }
14320
14377
 
14321
- var ignoreExports = /*@__PURE__*/ requireIgnore();
14322
- var ignore = /*@__PURE__*/getDefaultExportFromCjs(ignoreExports);
14378
+ const factory = options => new Ignore(options);
14379
+
14380
+ const isPathValid = path =>
14381
+ checkPath(path && checkPath.convert(path), path, RETURN_FALSE);
14382
+
14383
+ factory.isPathValid = isPathValid;
14384
+
14385
+
14386
+ // Windows
14387
+ // --------------------------------------------------------------
14388
+ /* istanbul ignore next */
14389
+ if (
14390
+ // Detect `process` so that it can run in browsers.
14391
+ typeof process !== 'undefined'
14392
+ && (
14393
+ process.env && process.env.IGNORE_TEST_WIN32
14394
+ || process.platform === 'win32'
14395
+ )
14396
+ ) {
14397
+ /* eslint no-control-regex: "off" */
14398
+ const makePosix = str => /^\\\\\?\\/.test(str)
14399
+ || /["<>|\u0000-\u001F]+/u.test(str)
14400
+ ? str
14401
+ : str.replace(/\\/g, '/');
14402
+
14403
+ checkPath.convert = makePosix;
14404
+
14405
+ // 'C:\\foo' <- 'C:\\foo' has been converted to 'C:/'
14406
+ // 'd:\\foo'
14407
+ const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
14408
+ checkPath.isNotRelative = path =>
14409
+ REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path)
14410
+ || isNotRelative(path);
14411
+ }
14323
14412
 
14324
14413
  function importFunction(id) {
14325
14414
  return import(id);
@@ -14391,8 +14480,8 @@ exports.defineConfig = defineConfig;
14391
14480
  exports.definePlugin = definePlugin;
14392
14481
  exports.ensureError = ensureError;
14393
14482
  exports.esmResolver = esmResolver;
14483
+ exports.factory = factory;
14394
14484
  exports.getFormatter = getFormatter;
14395
- exports.ignore = ignore;
14396
14485
  exports.isThenable = isThenable;
14397
14486
  exports.keywordPatternMatcher = keywordPatternMatcher;
14398
14487
  exports.name = name;