html-minifier-next 8.0.0 → 8.1.1

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/README.md CHANGED
@@ -547,6 +547,14 @@ For partial HTML fragments (such as template includes, SSI fragments, or closing
547
547
 
548
548
  To validate complete HTML markup, use [the W3C validator](https://validator.w3.org/) or one of [several validator packages](https://meiert.com/blog/html-validator-packages/).
549
549
 
550
+ ### Regex options and flags
551
+
552
+ `customAttrAssign` and `customAttrSurround` patterns are merged into one attribute pattern which carries no flags of its own. `i` and `s` are written into each pattern’s source instead, so they survive the merge. `u`, `v`, and `m` cannot be, and none of them fails loudly when dropped: `u` and `v` only narrow what syntax is legal, so a source valid under either stays valid without it and quietly matches something else—a dropped `u` leaves `\p{L}` matching the literal text `p{L}`—while a dropped `m` leaves `^` and `$` matching at the ends of the input rather than of each line.
553
+
554
+ A pattern is therefore refused with an error where the flag changes what its source matches—a property or code point escape, a character past the BMP, a character `i` folds by Unicode rules only while `u` is there (`/s/iu` matches `\u017F`, `/k/iu` matches `\u212A`), a `v` class that nests, subtracts, intersects, or holds strings, or—under `m`—an anchor whose meaning moves. A flag the source does not depend on, as in `/x=/u`, is left alone.
555
+
556
+ Patterns given as strings, in a configuration file or on the command line, may be written either bare (`ng-class`) or delimited with flags (`/ng-class/i`).
557
+
550
558
  ## Security
551
559
 
552
560
  ### ReDoS protection
@@ -555,7 +563,11 @@ You can use `ignoreCustomFragments` to hand HTML Minifier Next a regular express
555
563
 
556
564
  * Matching without backtracking: A pattern that wraps an any-character or negated-class body in literal delimiters—`<%[\s\S]*?%>` or `\{\{[^}]*?\}\}`, and every other shape below—is matched by scanning for those delimiters in linear time, with no regular expression involved. Patterns of other shapes run as regular expressions, one per pattern, so each keeps its own flags.
557
565
 
558
- * Pattern detection: HMN warns about the shapes that backtrack catastrophically—an unlimited quantifier over a group that itself contains a quantifier that can vary (`(a+)+`, `(a?)+`) or alternation (`(a|b)*`), and the same atom repeated unboundedly twice in a row (`.*.*`). A fixed count does not vary, so `(?:a{4})+` passes. These are also shapes a linear scan cannot stand in for. `strictCustomFragments` refuses them with an error instead, which is worth enabling where the patterns or the input are not entirely under your control. A pattern longer than 10,000 characters or nested more than 50 groups deep is judged risky without being analyzed further, so that reading the pattern cannot itself become the expensive step.
566
+ * Pattern detection: HMN warns about the shapes that backtrack catastrophically—an unlimited quantifier over a group that itself contains a quantifier that can vary (`(a+)+`, `(a?)+`) or alternation (`(a|b)*`), and two unbounded repeats that can consume the same character with only atoms matching empty between them (`.*.*`, `[a]*a*`, `\w*\d*`, `a*b*a*`).
567
+
568
+ A group is no wall: It counts by what its body can match, and repeats meet across its boundary, so `\s*(\w*)\s*` and `(a*)a*` are flagged like `\s*\w*\s*` and `a*a*`. A lookaround backtracks nothing, so `(?=a*)a*` passes. A fixed count does not vary, so `(?:a{4})+` passes; repeats that share no character leave nothing ambiguous to split, so `\s*\S*` passes. A pattern is read the way its own flags make it match, so `/.*\n*/s` and `/[a]*A*/i` are flagged where those same sources without the flags are not. Under `v`, a class that nests reads as the union it is, while one that subtracts (`--`) or intersects (`&&`) is left unread and passes.
569
+
570
+ These are also shapes a linear scan cannot stand in for. `strictCustomFragments` refuses them with an error instead, which is worth enabling where the patterns or the input are not entirely under your control. A pattern longer than 10,000 characters or nested more than 50 groups deep is judged risky without being analyzed further, so that reading the pattern cannot itself become the expensive step. The check reads shapes, not languages: It warns about the common ones rather than proving a pattern linear, and misses repeats that overlap only across whole subexpressions (`(ab)*(abab)*`). **Treat a pattern that passes as unflagged, not as vetted.**
559
571
 
560
572
  * Input length limits: The `maxInputLength` option allows you to set a maximum input size to prevent processing of excessively large inputs that could cause performance issues.
561
573
 
@@ -579,7 +591,8 @@ ignoreCustomFragments: [
579
591
  ignoreCustomFragments: [
580
592
  /<%(\s|\S)*?%>/, // Unlimited quantifier over an alternating group
581
593
  /\{\{([^}]+)+\}\}/, // Nested unlimited quantifiers
582
- /<!--[\s\S]*[\s\S]*-->/ // The same atom repeated unboundedly twice
594
+ /<!--[\s\S]*[\s\S]*-->/, // Two unbounded repeats in a row
595
+ /<%\w*\d*%>/ // Two unbounded repeats over overlapping sets
583
596
  ]
584
597
  ```
585
598
 
@@ -1 +1 @@
1
- {"version":3,"file":"htmlminifier.d.ts","sourceRoot":"","sources":["../../src/htmlminifier.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"htmlminifier.d.ts","sourceRoot":"","sources":["../../src/htmlminifier.js"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"htmlparser.d.ts","sourceRoot":"","sources":["../../src/htmlparser.js"],"names":[],"mappings":"AAcG,YAWG,iBAAiB,GAXV;IACR,KAAK,CAAC,WAAW;IACjB,GAAG,CAAC,WAAW;IACf,KAAK,CAAC,WAAW;IACjB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,WAAW;IACnB,oBAAoB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3C,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACnC,kBAAkB,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,SAAS,CAAC;IAC5C,gBAAgB,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;CACxC,CAAmB;AAyCvB,eAAO,MAAM,MAAM,QAA8C,CAAC;AA2HlE,qBAAa,UAAU;IAMd,IAAI;IACJ,OAAO;IANd;;;OAGG;IACH,YAAY,IAAI,EAHL,MAGK,EAAE,OAAO,EAFd,iBAEc,EAGxB;IAEK,KAAK,kBAypBV;CACF"}
1
+ {"version":3,"file":"htmlparser.d.ts","sourceRoot":"","sources":["../../src/htmlparser.js"],"names":[],"mappings":"AAcG,YAWG,iBAAiB,GAXV;IACR,KAAK,CAAC,WAAW;IACjB,GAAG,CAAC,WAAW;IACf,KAAK,CAAC,WAAW;IACjB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,WAAW;IACnB,oBAAoB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3C,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACnC,kBAAkB,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,SAAS,CAAC;IAC5C,gBAAgB,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;CACxC,CAAmB;AAyCvB,eAAO,MAAM,MAAM,QAA8C,CAAC;AA2HlE,qBAAa,UAAU;IAMd,IAAI;IACJ,OAAO;IANd;;;OAGG;IACH,YAAY,IAAI,EAHL,MAGK,EAAE,OAAO,EAFd,iBAEc,EAGxB;IAEK,KAAK,kBAmqBV;CACF"}
@@ -1,5 +1,7 @@
1
1
  declare const RE_WS_START: RegExp;
2
2
  declare const RE_WS_END: RegExp;
3
+ declare const RE_WS_ONLY: RegExp;
4
+ declare const RE_WS_NBSP_END: RegExp;
3
5
  declare const RE_ALL_WS_NBSP: RegExp;
4
6
  declare const RE_NBSP_LEADING_GROUP: RegExp;
5
7
  declare const RE_NBSP_LEAD_GROUP: RegExp;
@@ -95,5 +97,5 @@ declare const looseElements: Set<string>;
95
97
  declare const trailingElements: Set<string>;
96
98
  declare const htmlElements: Set<string>;
97
99
  declare const specialContentElements: Set<string>;
98
- export { RE_WS_START, RE_WS_END, RE_ALL_WS_NBSP, RE_NBSP_LEADING_GROUP, RE_NBSP_LEAD_GROUP, RE_NBSP_TRAILING_GROUP, RE_NBSP_TRAILING_STRIP, RE_EVENT_ATTR_DEFAULT, RE_CAN_REMOVE_ATTR_QUOTES, RE_TRAILING_SEMICOLON, RE_AMP_ENTITY, RE_LEGACY_ENTITIES, RE_ESCAPE_LT, RE_ATTR_WS_CHECK, RE_ATTR_WS_COLLAPSE, RE_ATTR_WS_TRIM, RE_STYLE_ELEMENT, RE_EMPTY_ATTRIBUTE, inlineElementsToKeepWhitespaceAround, inlineElementsToKeepWhitespaceWithin, inlineElementsToKeepWhitespace, formControlElements, generalDefaults, tagDefaults, executableScriptsMimetypes, keepScriptsMimetypes, jsonScriptTypes, isSimpleBoolean, isBooleanValue, collapsibleValues, srcsetElements, optionalStartTags, optionalEndTags, headerElements, descriptionElements, pBlockElements, pInlineElements, rubyEndTagOmission, rubyRtcEndTagOmission, optionElements, tableContentElements, tableSectionElements, cellElements, topLevelElements, compactElements, looseElements, trailingElements, htmlElements, specialContentElements };
100
+ export { RE_WS_START, RE_WS_END, RE_WS_ONLY, RE_WS_NBSP_END, RE_ALL_WS_NBSP, RE_NBSP_LEADING_GROUP, RE_NBSP_LEAD_GROUP, RE_NBSP_TRAILING_GROUP, RE_NBSP_TRAILING_STRIP, RE_EVENT_ATTR_DEFAULT, RE_CAN_REMOVE_ATTR_QUOTES, RE_TRAILING_SEMICOLON, RE_AMP_ENTITY, RE_LEGACY_ENTITIES, RE_ESCAPE_LT, RE_ATTR_WS_CHECK, RE_ATTR_WS_COLLAPSE, RE_ATTR_WS_TRIM, RE_STYLE_ELEMENT, RE_EMPTY_ATTRIBUTE, inlineElementsToKeepWhitespaceAround, inlineElementsToKeepWhitespaceWithin, inlineElementsToKeepWhitespace, formControlElements, generalDefaults, tagDefaults, executableScriptsMimetypes, keepScriptsMimetypes, jsonScriptTypes, isSimpleBoolean, isBooleanValue, collapsibleValues, srcsetElements, optionalStartTags, optionalEndTags, headerElements, descriptionElements, pBlockElements, pInlineElements, rubyEndTagOmission, rubyRtcEndTagOmission, optionElements, tableContentElements, tableSectionElements, cellElements, topLevelElements, compactElements, looseElements, trailingElements, htmlElements, specialContentElements };
99
101
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/lib/constants.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/lib/constants.js"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../../src/lib/options.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../../src/lib/options.js"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@
2
2
  * Collect the class names and IDs a document references.
3
3
  *
4
4
  * Style sheet contents are excluded, so that a style sheet never counts as
5
- * evidence for its own selectors. An `iframe srcdoc` value is not scanned
5
+ * evidence for its own selectors. An `iframe srcdoc` value is not scanned,
6
6
  * either: It holds a document of its own, minified against its own symbol set.
7
7
  * Over-collecting is safe here (a symbol wrongly considered used is merely
8
8
  * kept), under-collecting is not.
@@ -51,6 +51,17 @@ declare function lowercase(value: string): string;
51
51
  declare function replaceAsync(str: string, regex: RegExp, asyncFn: Function): Promise<string>;
52
52
  /** @param {string | RegExp} value */
53
53
  declare function parseRegExp(value: string | RegExp): RegExp | undefined;
54
+ /**
55
+ * Which flag changes what a source matches, so that embedding the source where the
56
+ * flag cannot follow would silently match something else. `u` and `v` only narrow
57
+ * what syntax is legal, so a source valid under either stays valid without it—the
58
+ * difference never surfaces as a syntax error, and this stands in for the one that
59
+ * would otherwise be raised.
60
+ * @param {RegExp} pattern
61
+ * @returns {'u' | 'v' | 'm' | null} The flag the source depends on, or `null` where
62
+ * every flag it carries would leave the source matching the same
63
+ */
64
+ declare function lostFlag(pattern: RegExp): 'u' | 'v' | 'm' | null;
54
65
  /**
55
66
  * A pattern’s source, rewritten to match the way its own flags make it match, for
56
67
  * embedding in a larger regex that cannot carry them. `i` and `s` fit into a
@@ -62,12 +73,17 @@ declare function parseRegExp(value: string | RegExp): RegExp | undefined;
62
73
  * @returns {string}
63
74
  */
64
75
  declare function embedSource(pattern: RegExp): string;
76
+ export type Repeat = {
77
+ text: string;
78
+ ranges: [number, number][] | null;
79
+ };
65
80
  /**
66
- * @param {string} source - Regex source to judge
81
+ * @param {RegExp | string} pattern - Pattern to judge, or a bare source to read
82
+ * as it stands
67
83
  * @returns {string | null} What makes the pattern a backtracking risk, phrased
68
84
  * to follow the pattern itself, or `null` where it is none
69
85
  */
70
- declare function describeQuantifierRisk(source: string): string | null;
86
+ declare function describeQuantifierRisk(pattern: RegExp | string): string | null;
71
87
  /**
72
88
  * Find the index of the `>` that closes an opening tag, correctly skipping
73
89
  * over quoted attribute values (which may contain `>`).
@@ -76,5 +92,5 @@ declare function describeQuantifierRisk(source: string): string | null;
76
92
  * @returns {number} Index of the closing `>`, or -1 if not found
77
93
  */
78
94
  declare function findTagEnd(html: string, pos: number): number;
79
- export { stableStringify, findTagEnd, LRU, MAX_CACHE_ENTRY_SIZE, hashContent, uniqueId, identity, isThenable, lowercase, replaceAsync, parseRegExp, embedSource, describeQuantifierRisk };
95
+ export { stableStringify, findTagEnd, LRU, MAX_CACHE_ENTRY_SIZE, hashContent, uniqueId, identity, isThenable, lowercase, replaceAsync, parseRegExp, embedSource, lostFlag, describeQuantifierRisk };
80
96
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/lib/utils.js"],"names":[],"mappings":"AAEA;;;GAGG;AACH,iBAAS,eAAe,CAAC,GAAG,EAHjB,OAGiB,GAFf,MAAM,CAYlB;AAID,cAAM,GAAG;IAEA,KAAK;IACL,IAAI;IACJ,IAAI;IACT,mCAAmC;IAC9B,GAAG,EADG,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC;IAJjC,YAAY,KAAK,SAAM,EAMtB;IACD,0BAA0B;IAC1B,GAAG,CAAC,GAAG,EADK,MACL,WAUN;IACD;;;OAGG;IACH,GAAG,CAAC,GAAG,EAHI,MAGJ,EAAE,KAAK,EAFH,OAEG,QAOb;IACD,0BAA0B;IAC1B,MAAM,CAAC,GAAG,EADE,MACF,QAA2B;IACrC,6EAA6E;IAC7E,KAAK,IADS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAGxE;CACF;AAKD,QAAA,MAAM,oBAAoB,QAAc,CAAC;AAIzC,0BAA0B;AAC1B,iBAAS,WAAW,CAAC,GAAG,EADZ,MACY,UAOvB;AAID,4BAA4B;AAC5B,iBAAS,QAAQ,CAAC,KAAK,EADX,MACW,UAMtB;AAID,4BAA4B;AAC5B,iBAAS,QAAQ,CAAC,KAAK,EADX,MACW,UAEtB;AAED;;;GAGG;AACH,iBAAS,UAAU,CAAC,KAAK,EAHd,OAGc,GAFZ,KAAK,IAAI,WAAW,CAAC,GAAG,CAAC,CAIrC;AAED,4BAA4B;AAC5B,iBAAS,SAAS,CAAC,KAAK,EADZ,MACY,UAEvB;AAID;;;;;;GAMG;AACH,iBAAe,YAAY,CAAC,GAAG,EALpB,MAKoB,EAAE,KAAK,EAJ3B,MAI2B,EAAE,OAAO,UAAA,GAFlC,OAAO,CAAC,MAAM,CAAC,CAe3B;AAID,qCAAqC;AACrC,iBAAS,WAAW,CAAC,KAAK,EADd,MAAM,GAAG,MACK,sBAUzB;AAqLD;;;;;;;;;GASG;AACH,iBAAS,WAAW,CAAC,OAAO,EAHjB,MAGiB,GAFf,MAAM,CAOlB;AAyFD;;;;GAIG;AACH,iBAAS,sBAAsB,CAAC,MAAM,EAJ3B,MAI2B,GAHzB,MAAM,GAAG,IAAI,CAezB;AAED;;;;;;GAMG;AACH,iBAAS,UAAU,CAAC,IAAI,EAJb,MAIa,EAAE,GAAG,EAHlB,MAGkB,GAFhB,MAAM,CAelB;AAID,OAAO,EACL,eAAe,EACf,UAAU,EACV,GAAG,EACH,oBAAoB,EACpB,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,SAAS,EACT,YAAY,EACZ,WAAW,EACX,WAAW,EACX,sBAAsB,EACvB,CAAC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/lib/utils.js"],"names":[],"mappings":"AAEA;;;GAGG;AACH,iBAAS,eAAe,CAAC,GAAG,EAHjB,OAGiB,GAFf,MAAM,CAYlB;AAID,cAAM,GAAG;IAEA,KAAK;IACL,IAAI;IACJ,IAAI;IACT,mCAAmC;IAC9B,GAAG,EADG,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC;IAJjC,YAAY,KAAK,SAAM,EAMtB;IACD,0BAA0B;IAC1B,GAAG,CAAC,GAAG,EADK,MACL,WAUN;IACD;;;OAGG;IACH,GAAG,CAAC,GAAG,EAHI,MAGJ,EAAE,KAAK,EAFH,OAEG,QAOb;IACD,0BAA0B;IAC1B,MAAM,CAAC,GAAG,EADE,MACF,QAA2B;IACrC,6EAA6E;IAC7E,KAAK,IADS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAGxE;CACF;AAKD,QAAA,MAAM,oBAAoB,QAAc,CAAC;AAIzC,0BAA0B;AAC1B,iBAAS,WAAW,CAAC,GAAG,EADZ,MACY,UAOvB;AAID,4BAA4B;AAC5B,iBAAS,QAAQ,CAAC,KAAK,EADX,MACW,UAMtB;AAID,4BAA4B;AAC5B,iBAAS,QAAQ,CAAC,KAAK,EADX,MACW,UAEtB;AAED;;;GAGG;AACH,iBAAS,UAAU,CAAC,KAAK,EAHd,OAGc,GAFZ,KAAK,IAAI,WAAW,CAAC,GAAG,CAAC,CAIrC;AAED,4BAA4B;AAC5B,iBAAS,SAAS,CAAC,KAAK,EADZ,MACY,UAEvB;AAID;;;;;;GAMG;AACH,iBAAe,YAAY,CAAC,GAAG,EALpB,MAKoB,EAAE,KAAK,EAJ3B,MAI2B,EAAE,OAAO,UAAA,GAFlC,OAAO,CAAC,MAAM,CAAC,CAe3B;AAID,qCAAqC;AACrC,iBAAS,WAAW,CAAC,KAAK,EADd,MAAM,GAAG,MACK,sBAUzB;AAyYD;;;;;;;;;GASG;AACH,iBAAS,QAAQ,CAAC,OAAO,EAJd,MAIc,GAHZ,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,CAqDlC;AAED;;;;;;;;;GASG;AACH,iBAAS,WAAW,CAAC,OAAO,EAHjB,MAGiB,GAFf,MAAM,CAOlB;AAGE,YAA6D,MAAM,GAAzD;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,IAAI,CAAA;CAAC,CAE7D;AAqKD;;;;;GAKG;AACH,iBAAS,sBAAsB,CAAC,OAAO,EAL5B,MAAM,GAAG,MAKmB,GAH1B,MAAM,GAAG,IAAI,CAqBzB;AAED;;;;;;GAMG;AACH,iBAAS,UAAU,CAAC,IAAI,EAJb,MAIa,EAAE,GAAG,EAHlB,MAGkB,GAFhB,MAAM,CAelB;AAID,OAAO,EACL,eAAe,EACf,UAAU,EACV,GAAG,EACH,oBAAoB,EACpB,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,SAAS,EACT,YAAY,EACZ,WAAW,EACX,WAAW,EACX,QAAQ,EACR,sBAAsB,EACvB,CAAC"}
package/package.json CHANGED
@@ -95,5 +95,5 @@
95
95
  },
96
96
  "type": "module",
97
97
  "types": "dist/types/htmlminifier.d.ts",
98
- "version": "8.0.0"
98
+ "version": "8.1.1"
99
99
  }
@@ -8,6 +8,10 @@ import { collectUsedSymbols } from './lib/unused-css.js';
8
8
  import {
9
9
  RE_LEGACY_ENTITIES,
10
10
  RE_ESCAPE_LT,
11
+ RE_WS_START,
12
+ RE_WS_END,
13
+ RE_WS_ONLY,
14
+ RE_WS_NBSP_END,
11
15
  RE_STYLE_ELEMENT,
12
16
  inlineElementsToKeepWhitespaceAround,
13
17
  inlineElementsToKeepWhitespaceWithin,
@@ -574,6 +578,8 @@ const DEFAULT_JS_TYPES = new Set(['', 'text/javascript', 'application/javascript
574
578
  // Pre-compiled patterns for buffer scanning
575
579
  const RE_START_TAG = /^<[^/!]/;
576
580
  const RE_END_TAG = /^<\//;
581
+ const RE_WHITESPACE_ONLY = /^\s+$/;
582
+ const RE_TRAILING_WHITESPACE = /\s$/;
577
583
 
578
584
  // Pre-compiled patterns for `htmlmin:ignore` block content analysis
579
585
  const RE_HTML_COMMENT_START = /^\s*<!--/;
@@ -1117,11 +1123,57 @@ async function minifyHTML(value, options, partialMarkup) {
1117
1123
  buffer.length = Math.max(0, index);
1118
1124
  }
1119
1125
 
1126
+ // Whether whitespace-only text between tags survives minification
1127
+ function keepsWhitespace() {
1128
+ return !options.collapseWhitespace || Boolean(options.conservativeCollapse) || Boolean(options.preserveLineBreaks);
1129
+ }
1130
+
1131
+ // The two whitespace runs an omitted tag separated become one run, which collapses to the
1132
+ // single character it is due: a line break under `preserveLineBreaks`, a no-break space
1133
+ // where the run holds one, a space under `conservativeCollapse`
1134
+ function mergeWhitespaceRuns(/** @type {number} */ index) {
1135
+ let before = index - 1;
1136
+ while (before >= 0 && buffer[before] === '') {
1137
+ before--;
1138
+ }
1139
+ let after = index;
1140
+ while (after < buffer.length && buffer[after] === '') {
1141
+ after++;
1142
+ }
1143
+ if (before < 0 || after >= buffer.length) {
1144
+ return;
1145
+ }
1146
+ const prev = buffer[before] ?? '';
1147
+ const next = buffer[after] ?? '';
1148
+ if (!RE_WS_NBSP_END.test(prev) || !RE_WS_START.test(next)) {
1149
+ return;
1150
+ }
1151
+ // A line break the run already carries outranks the whitespace ahead of it; trimming
1152
+ // the other side takes ASCII whitespace only, so a no-break space stays as is
1153
+ if (options.preserveLineBreaks && /[\n\r]/.test((next.match(RE_WS_START) ?? [''])[0])) {
1154
+ buffer[before] = prev.replace(RE_WS_END, '');
1155
+ } else {
1156
+ buffer[after] = next.replace(RE_WS_START, '');
1157
+ }
1158
+ }
1159
+
1120
1160
  function removeEndTag() {
1121
1161
  let index = buffer.length - 1;
1122
1162
  while (index > 0 && !RE_END_TAG.test(buffer[index] ?? '')) {
1123
1163
  index--;
1124
1164
  }
1165
+ // Text that follows the end tag is kept as is, so drop the tag alone. Whitespace kept
1166
+ // verbatim—in `pre`, in `textarea`, or wherever `canTrimWhitespace` says so—counts as
1167
+ // kept text too, no matter how whitespace is collapsed elsewhere.
1168
+ const noTrim = stackNoTrimWhitespace.length > 0;
1169
+ if (index < buffer.length - 1 && (keepsWhitespace() || noTrim) && RE_END_TAG.test(buffer[index] ?? '')) {
1170
+ buffer.splice(index, 1);
1171
+ // Only collapsed whitespace can merge
1172
+ if (options.collapseWhitespace && !noTrim) {
1173
+ mergeWhitespaceRuns(index);
1174
+ }
1175
+ return;
1176
+ }
1125
1177
  buffer.length = Math.max(0, index);
1126
1178
  }
1127
1179
 
@@ -1143,11 +1195,15 @@ async function minifyHTML(value, options, partialMarkup) {
1143
1195
  // element which has now been removed
1144
1196
  function squashTrailingWhitespace(/** @type {string} */ nextTag) {
1145
1197
  let charsIndex = buffer.length - 1;
1146
- if (buffer.length > 1) {
1147
- const item = buffer[buffer.length - 1] ?? '';
1148
- if (/^(?:<!|$)/.test(item) && (!uidIgnore || item.indexOf(uidIgnore) === -1)) {
1149
- charsIndex--;
1198
+ // Step back over every trailing comment and empty entry, not just one—a single step
1199
+ // stops the search at the comment itself, leaving whitespace behind that a second
1200
+ // minification run then removes
1201
+ while (charsIndex > 0) {
1202
+ const item = buffer[charsIndex] ?? '';
1203
+ if (!/^(?:<!|$)/.test(item) || (uidIgnore && item.indexOf(uidIgnore) !== -1)) {
1204
+ break;
1150
1205
  }
1206
+ charsIndex--;
1151
1207
  }
1152
1208
  trimTrailingWhitespace(charsIndex, nextTag);
1153
1209
  }
@@ -1240,7 +1296,7 @@ async function minifyHTML(value, options, partialMarkup) {
1240
1296
  } else {
1241
1297
  text = collapseWhitespace(text, options, true, true);
1242
1298
  }
1243
- if (!text && /\s$/.test(currentChars) && textPrevTag && textPrevTag.charAt(0) === '/') {
1299
+ if (!text && RE_TRAILING_WHITESPACE.test(currentChars) && textPrevTag && textPrevTag.charAt(0) === '/') {
1244
1300
  trimTrailingWhitespace(buffer.length - 1, textNextTag);
1245
1301
  }
1246
1302
  }
@@ -1277,9 +1333,16 @@ async function minifyHTML(value, options, partialMarkup) {
1277
1333
  // `</head>`, `</colgroup>`, or `</caption>` may be omitted if not followed by space or comment
1278
1334
  if (optionalEndTagEmitted && (compactElements.has(optionalEndTag) || (looseElements.has(optionalEndTag) && !/^\s/.test(effectiveText)))) {
1279
1335
  removeEndTag();
1336
+ // The tag is gone from the buffer, so nothing is left to omit later
1337
+ optionalEndTag = '';
1338
+ optionalEndTagEmitted = false;
1280
1339
  }
1281
- // Don’t reset `optionalEndTag` if text is only whitespace and will be collapsed (not conservatively)
1282
- if (!/^\s+$/.test(text) || !options.collapseWhitespace || options.conservativeCollapse) {
1340
+ // Whitespace-only text doesn’t prevent omission of the pending end tag: It’s either
1341
+ // collapsed away, or kept in place while the tag alone is dropped—except for
1342
+ // `</head>`, `</colgroup>`, and `</caption>`, which may not be followed by space.
1343
+ // Only ASCII whitespace counts, as no-break space and friends are content
1344
+ // https://html.spec.whatwg.org/multipage/syntax.html#optional-tags
1345
+ if (!RE_WS_ONLY.test(text) || (keepsWhitespace() && looseElements.has(optionalEndTag))) {
1283
1346
  optionalEndTag = '';
1284
1347
  optionalEndTagEmitted = false;
1285
1348
  }
@@ -1347,7 +1410,7 @@ async function minifyHTML(value, options, partialMarkup) {
1347
1410
  const prevComment = buffer[buffer.length - 2];
1348
1411
 
1349
1412
  // Check if previous item is whitespace-only and item before that is ignore-placeholder
1350
- if (prevText && /^\s+$/.test(prevText) && prevComment && uidIgnorePlaceholderPattern.test(prevComment)) {
1413
+ if (prevText && RE_WHITESPACE_ONLY.test(prevText) && prevComment && uidIgnorePlaceholderPattern.test(prevComment)) {
1351
1414
  // Extract the index from both placeholders to check their content
1352
1415
  const currentMatch = comment.match(uidIgnorePlaceholderPattern);
1353
1416
  const prevMatch = prevComment.match(uidIgnorePlaceholderPattern);
@@ -1390,7 +1453,11 @@ async function minifyHTML(value, options, partialMarkup) {
1390
1453
  // Apply `collapseWhitespace` with appropriate context
1391
1454
  if (!stackNoTrimWhitespace.length && !stackNoCollapseWhitespace.length) {
1392
1455
  // Not in pre or other no-collapse context
1393
- if (options.preserveLineBreaks && /[\n\r]/.test(prevText)) {
1456
+ if (prevText.includes('\xA0')) {
1457
+ // No-break spaces are content, so every one of them survives in place;
1458
+ // the ASCII whitespace around them collapses as it would anywhere else
1459
+ collapsedText = collapseWhitespace(prevText, options, true, true, true);
1460
+ } else if (options.preserveLineBreaks && /[\n\r]/.test(prevText)) {
1394
1461
  // Preserve line break as single newline
1395
1462
  collapsedText = '\n';
1396
1463
  } else if (options.conservativeCollapse) {
@@ -1639,10 +1706,11 @@ async function minifyHTML(value, options, partialMarkup) {
1639
1706
  removeStartTag();
1640
1707
  }
1641
1708
  optionalStartTag = '';
1642
- // `</html>` or `</body>` may be omitted if not followed by comment
1643
- // `</head>` may be omitted if not followed by space or comment
1644
- // `</p>` may be omitted if no more content in parent, unless parent is in `pInlineElements` or is a custom element
1645
- // https://html.spec.whatwg.org/multipage/syntax.html#optional-tags
1709
+ // `</html>` or `</body>` may be omitted if not followed by comment,
1710
+ // `</head>` may be omitted if not followed by space or comment,
1711
+ // `</p>` may be omitted if no more content in parent,
1712
+ // unless parent is in `pInlineElements` or is a custom element
1713
+ // (https://html.spec.whatwg.org/multipage/syntax.html#optional-tags);
1646
1714
  // except for `</dt>` or `</thead>`, end tags may be omitted if no more content in parent element
1647
1715
  if (tag && optionalEndTag && optionalEndTagEmitted && !trailingElements.has(optionalEndTag) && (optionalEndTag !== 'p' || (!pInlineElements.has(tag) && !tag.includes('-')))) {
1648
1716
  removeEndTag();
package/src/htmlparser.js CHANGED
@@ -703,7 +703,17 @@ export class HTMLParser {
703
703
 
704
704
  if (lastTagLower === 'p' && nonPhrasing.has(lowerTagName)) {
705
705
  await parseEndTag('', lastTag);
706
- } else if (lowerTagName === 'tbody') {
706
+ }
707
+ // A row or cell start tag closes the row or cell still open in the same table,
708
+ // which may sit deeper down the stack than the element that was opened last
709
+ if (lowerTagName === 'tr') {
710
+ closeIfFoundInCurrentTable('tr');
711
+ } else if (lowerTagName === 'td' || lowerTagName === 'th') {
712
+ if (!closeIfFoundInCurrentTable('td')) {
713
+ closeIfFoundInCurrentTable('th');
714
+ }
715
+ }
716
+ if (lowerTagName === 'tbody') {
707
717
  if (!closeIfFoundInCurrentTable('tfoot')) {
708
718
  closeIfFoundInCurrentTable('thead');
709
719
  }
@@ -2,6 +2,8 @@
2
2
 
3
3
  const RE_WS_START = /^[ \n\r\t\f]+/;
4
4
  const RE_WS_END = /[ \n\r\t\f]+$/;
5
+ const RE_WS_ONLY = /^[ \n\r\t\f]+$/;
6
+ const RE_WS_NBSP_END = /[ \n\r\t\f\xA0]$/;
5
7
  const RE_ALL_WS_NBSP = /[ \n\r\t\f\xA0]+/g;
6
8
  const RE_NBSP_LEADING_GROUP = /(^|\xA0+)[^\xA0]+/g;
7
9
  const RE_NBSP_LEAD_GROUP = /(\xA0+)[^\xA0]+/g;
@@ -176,6 +178,8 @@ export {
176
178
  // Regex patterns
177
179
  RE_WS_START,
178
180
  RE_WS_END,
181
+ RE_WS_ONLY,
182
+ RE_WS_NBSP_END,
179
183
  RE_ALL_WS_NBSP,
180
184
  RE_NBSP_LEADING_GROUP,
181
185
  RE_NBSP_LEAD_GROUP,
@@ -225,7 +225,7 @@ function replaceCustomFragments(value, fragments, replacer) {
225
225
  ? previous
226
226
  : ('open' in fragment ? scan(fragment, i, from, false) : run(fragment, from, false));
227
227
  found[i] = match;
228
- // Nothing ahead now means nothing ahead later either
228
+ // Nothing ahead now means nothing ahead later, either
229
229
  if (!match) exhausted[i] = true;
230
230
  }
231
231
 
@@ -1,5 +1,5 @@
1
1
  import { createUrlMinifier } from './urls.js';
2
- import { LRU, MAX_CACHE_ENTRY_SIZE, stableStringify, hashContent, identity, lowercase, replaceAsync, parseRegExp, describeQuantifierRisk } from './utils.js';
2
+ import { LRU, MAX_CACHE_ENTRY_SIZE, stableStringify, hashContent, identity, lowercase, replaceAsync, parseRegExp, describeQuantifierRisk, lostFlag } from './utils.js';
3
3
  import { RE_TRAILING_SEMICOLON } from './constants.js';
4
4
  import { canCollapseWhitespace, canTrimWhitespace } from './whitespace.js';
5
5
  import { wrapCSS, unwrapCSS } from './content.js';
@@ -602,6 +602,25 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, getS
602
602
  }
603
603
  });
604
604
 
605
+ // The parser merges these into one attribute pattern, and a merged pattern
606
+ // carries no flags of its own. `i` and `s` are written into the source instead,
607
+ // but `u` and `v` change how a source reads and cannot be: dropped, `\p{L}`
608
+ // stops being a property escape and matches the literal text `p{L}`, silently
609
+ // and without failing to compile. Refusing them beats matching the wrong thing.
610
+ /** @type {[string, RegExp[]][]} */
611
+ const merged = [
612
+ ['customAttrAssign', options.customAttrAssign || []],
613
+ ['customAttrSurround', (options.customAttrSurround || []).flat()]
614
+ ];
615
+ for (const [key, patterns] of merged) {
616
+ for (const re of patterns) {
617
+ if (!(re instanceof RegExp)) continue;
618
+ const flag = lostFlag(re);
619
+ if (!flag) continue;
620
+ throw new Error(`HTML Minifier Next: \`${key}\` pattern \`/${re.source}/${re.flags}\` carries \`${flag}\`, which the merged attribute pattern cannot carry—rewrite it without \`${flag}\``);
621
+ }
622
+ }
623
+
605
624
  // Fragments that compound quantifiers or alternation under unbounded repetition
606
625
  // are the shapes that backtrack catastrophically, and they are also the ones a
607
626
  // linear scan cannot stand in for; so are patterns too long or too deeply
@@ -610,7 +629,7 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, getS
610
629
  // fragments have exactly that shape, so the check flagging it would mean
611
630
  // warning about the defaults themselves.
612
631
  for (const re of options.ignoreCustomFragments || []) {
613
- const risk = describeQuantifierRisk(re.source);
632
+ const risk = describeQuantifierRisk(re);
614
633
  if (!risk) continue;
615
634
  const problem = `Custom fragment \`/${re.source}/\` ${risk}`;
616
635
  if (options.strictCustomFragments) {
@@ -170,7 +170,7 @@ function findRawTextElements(haystack, tagName) {
170
170
  * Collect the class names and IDs a document references.
171
171
  *
172
172
  * Style sheet contents are excluded, so that a style sheet never counts as
173
- * evidence for its own selectors. An `iframe srcdoc` value is not scanned
173
+ * evidence for its own selectors. An `iframe srcdoc` value is not scanned,
174
174
  * either: It holds a document of its own, minified against its own symbol set.
175
175
  * Over-collecting is safe here (a symbol wrongly considered used is merely
176
176
  * kept), under-collecting is not.
package/src/lib/utils.js CHANGED
@@ -145,6 +145,67 @@ function parseRegExp(value) {
145
145
  return value;
146
146
  }
147
147
 
148
+ // Regex source reading, shared by the analysis and the rewriting below
149
+
150
+ const RE_ASCII_LETTER = /^[a-zA-Z]$/;
151
+ const RE_HEX_PAIR = /^[0-9a-fA-F]{2}$/;
152
+ const RE_HEX_QUAD = /^[0-9a-fA-F]{4}$/;
153
+
154
+ /**
155
+ * @param {string} source @param {number} index - Index of the opening `[`
156
+ * @param {boolean} [nested] - Whether the pattern carries `v`, where a class nests
157
+ * inside a class and the first `]` need not be the one that closes it
158
+ * @returns {number} Index just past the closing `]`
159
+ */
160
+ function skipCharacterClass(source, index, nested = false) {
161
+ let i = index + 1;
162
+ let open = 1;
163
+ while (i < source.length && open > 0) {
164
+ const char = source[i];
165
+ if (char === '\\') i += tokenLength(source, i);
166
+ else {
167
+ if (nested && char === '[') open++;
168
+ else if (char === ']') open--;
169
+ i++;
170
+ }
171
+ }
172
+ return i;
173
+ }
174
+
175
+ /**
176
+ * Length of the token at `index`, so that a multi-character escape is read whole
177
+ * rather than leaving its tail to be mistaken for literals.
178
+ * @param {string} source @param {number} index
179
+ */
180
+ function tokenLength(source, index) {
181
+ if (source[index] !== '\\') return 1;
182
+ const next = source[index + 1];
183
+ if (next === 'x' && RE_HEX_PAIR.test(source.slice(index + 2, index + 4))) return 4;
184
+ if (next === 'u') {
185
+ if (source[index + 2] === '{') {
186
+ const close = source.indexOf('}', index + 3);
187
+ if (close !== -1) return close - index + 1;
188
+ }
189
+ if (RE_HEX_QUAD.test(source.slice(index + 2, index + 6))) return 6;
190
+ }
191
+ if (next === 'c' && RE_ASCII_LETTER.test(source[index + 2] ?? '')) return 3;
192
+ // A `v` class holds string literals, which fold as strings or not at all
193
+ if (next === 'q' && source[index + 2] === '{') {
194
+ const close = source.indexOf('}', index + 3);
195
+ if (close !== -1) return close - index + 1;
196
+ }
197
+ // A property name or a group name is syntax, not text to fold
198
+ if ((next === 'p' || next === 'P') && source[index + 2] === '{') {
199
+ const close = source.indexOf('}', index + 3);
200
+ if (close !== -1) return close - index + 1;
201
+ }
202
+ if (next === 'k' && source[index + 2] === '<') {
203
+ const close = source.indexOf('>', index + 3);
204
+ if (close !== -1) return close - index + 1;
205
+ }
206
+ return 2;
207
+ }
208
+
148
209
  // ReDoS risk detection for user-supplied patterns
149
210
 
150
211
  // Quantifier following an atom, with its optional lazy `?`; the captures hold
@@ -164,14 +225,15 @@ const RE_TRANSPARENT_GROUP = /^\((?:\?:|\?<[^>=!][^>]*>)?([\s\S]*)\)$/;
164
225
  // A quantifier of exactly one repetition, which is notation rather than shape
165
226
  const RE_EXACT_ONE = /\{1(?:,1)?\}\??$/;
166
227
 
167
- /** @param {string} source @param {number} index - Index of the opening `[` */
168
- function skipCharacterClass(source, index) {
169
- let i = index + 1;
170
- while (i < source.length && source[i] !== ']') {
171
- i += source[i] === '\\' ? 2 : 1;
172
- }
173
- return i + 1;
174
- }
228
+ // An atom that matches without consuming, so what precedes it stays adjacent to
229
+ // what follows; a group that can match empty is left out, being rare enough that
230
+ // missing it costs less than reading every body for it
231
+ const RE_ZERO_WIDTH = /^(?:[$^]|\\[bB]|\((?:\?[=!]|\?<[=!]))/;
232
+
233
+ // How many repeats an atom may still be adjacent to across atoms matching empty;
234
+ // comparing against every one of them is what the bound keeps from turning
235
+ // quadratic on a pattern that is nothing but repeats
236
+ const MAX_REACHABLE = 50;
175
237
 
176
238
  /** @param {string} atom - Atom as it reads in the source */
177
239
  function unwrapAtom(atom) {
@@ -184,15 +246,156 @@ function unwrapAtom(atom) {
184
246
  return inner;
185
247
  }
186
248
 
249
+ // Two atoms that repeat unboundedly side by side split a run between them in as
250
+ // many ways as the run is long, whenever both can consume the same character.
251
+ // Comparing what they match, rather than how they are spelled, is what catches
252
+ // `[a]*a*` and `\w*\d*` alongside `a*a*`.
253
+
254
+ const MAX_CODE_POINT = 0x10FFFF;
255
+
256
+ /** @type {Record<string, [number, number][]>} */
257
+ const CLASS_ESCAPE_RANGES = {
258
+ d: [[0x30, 0x39]],
259
+ w: [[0x30, 0x39], [0x41, 0x5A], [0x5F, 0x5F], [0x61, 0x7A]],
260
+ s: [[0x09, 0x0D], [0x20, 0x20], [0xA0, 0xA0], [0x1680, 0x1680], [0x2000, 0x200A],
261
+ [0x2028, 0x2029], [0x202F, 0x202F], [0x205F, 0x205F], [0x3000, 0x3000], [0xFEFF, 0xFEFF]]
262
+ };
263
+
264
+ /** @type {Record<string, number>} */
265
+ const CONTROL_ESCAPE_CODES = { 0: 0x00, f: 0x0C, n: 0x0A, r: 0x0D, t: 0x09, v: 0x0B };
266
+
267
+ // An escape that stands for something other than one character of text
268
+ const RE_NON_CHARACTER_ESCAPE = /[bBdDkpPsSwW1-9]/;
269
+
270
+ /** @param {[number, number][]} ranges */
271
+ function complement(ranges) {
272
+ const sorted = [...ranges].sort((one, other) => one[0] - other[0]);
273
+ /** @type {[number, number][]} */
274
+ const out = [];
275
+ let next = 0;
276
+ for (const [low, high] of sorted) {
277
+ if (low > next) out.push([next, low - 1]);
278
+ next = Math.max(next, high + 1);
279
+ }
280
+ if (next <= MAX_CODE_POINT) out.push([next, MAX_CODE_POINT]);
281
+ return out;
282
+ }
283
+
284
+ // `.` as a bare source reads it, without the `s` flag the source cannot carry
285
+ const DOT_RANGES = complement([[0x0A, 0x0A], [0x0D, 0x0D], [0x2028, 0x2029]]);
286
+
287
+ /**
288
+ * @param {string} token - One token, as `tokenLength` measures it
289
+ * @param {boolean} [inClass] - Whether the token sits inside a character class
290
+ * @returns {[number, number][] | null} What it matches, or `null` where it is not
291
+ * one character of text
292
+ */
293
+ function tokenRanges(token, inClass = false) {
294
+ if (token[0] !== '\\') {
295
+ const code = token.codePointAt(0) ?? 0;
296
+ return String.fromCodePoint(code) === token ? [[code, code]] : null;
297
+ }
298
+
299
+ const kind = token[1] ?? '';
300
+ const named = CLASS_ESCAPE_RANGES[kind.toLowerCase()];
301
+ if (named) return kind === kind.toLowerCase() ? named : complement(named);
302
+
303
+ const control = CONTROL_ESCAPE_CODES[kind];
304
+ if (control !== undefined) return [[control, control]];
305
+
306
+ if (kind === 'x' || kind === 'u') {
307
+ const code = Number.parseInt(token[2] === '{' ? token.slice(3, -1) : token.slice(2), 16);
308
+ return Number.isNaN(code) || code > MAX_CODE_POINT ? null : [[code, code]];
309
+ }
310
+ if (kind === 'c') {
311
+ const code = token.charCodeAt(2) % 32;
312
+ return [[code, code]];
313
+ }
314
+ // `\b` asserts a word boundary on its own, and is a backspace inside a class
315
+ if (kind === 'b' && inClass) return [[0x08, 0x08]];
316
+ // `\q{…}` stands for whole strings, not for a character
317
+ if (token.startsWith('\\q{')) return null;
318
+ if (RE_NON_CHARACTER_ESCAPE.test(kind)) return null;
319
+
320
+ // The rest is punctuation escaped to be read as itself
321
+ const code = token.codePointAt(1) ?? 0;
322
+ return String.fromCodePoint(code) === token.slice(1) ? [[code, code]] : null;
323
+ }
324
+
325
+ /** @param {[number, number][] | null} ranges @returns {number | null} The one code point it holds */
326
+ function singleCode(ranges) {
327
+ const [range] = ranges ?? [];
328
+ return ranges?.length === 1 && range && range[0] === range[1] ? range[0] : null;
329
+ }
330
+
331
+ /**
332
+ * @param {string} atom - Atom as it reads in the source, already unwrapped
333
+ * @param {boolean} [nested] - Whether the pattern carries `v`
334
+ * @param {number} [depth] - Class nesting level of this call
335
+ * @returns {[number, number][] | null} What it matches, or `null` where it is not
336
+ * one character of text, or one this does not read
337
+ */
338
+ function atomRanges(atom, nested = false, depth = 0) {
339
+ if (depth > MAX_PATTERN_DEPTH) return null;
340
+ if (atom === '.') return DOT_RANGES;
341
+ if (atom[0] !== '[') return tokenRanges(atom);
342
+ if (!atom.endsWith(']')) return null;
343
+
344
+ let i = atom[1] === '^' ? 2 : 1;
345
+ const negated = i === 2;
346
+ const end = atom.length - 1;
347
+ /** @type {[number, number][]} */
348
+ const members = [];
349
+
350
+ while (i < end) {
351
+ // A `v` class nests, and nesting alone is a union to read through
352
+ if (nested && atom[i] === '[') {
353
+ const close = skipCharacterClass(atom, i, true);
354
+ const inner = atomRanges(atom.slice(i, close), true, depth + 1);
355
+ if (!inner) return null;
356
+ members.push(...inner);
357
+ i = close;
358
+ continue;
359
+ }
360
+ // Subtraction and intersection are not unions, and are left unread
361
+ //
362
+ // @@ Read `--` and `&&` as set difference and intersection
363
+ // (so that a `v` class built is compared rather than passed unjudged)
364
+ if (nested && (atom.startsWith('--', i) || atom.startsWith('&&', i))) return null;
365
+
366
+ const fromLength = tokenLength(atom, i);
367
+ const from = tokenRanges(atom.slice(i, i + fromLength), true);
368
+ if (!from) return null;
369
+ i += fromLength;
370
+
371
+ // A dash right before the closing `]` is a member, not the start of a range
372
+ if (atom[i] !== '-' || i + 1 >= end) {
373
+ members.push(...from);
374
+ continue;
375
+ }
376
+ const toLength = tokenLength(atom, i + 1);
377
+ // Only single characters bound a range; `\d-z` is not a range at all
378
+ const low = singleCode(from);
379
+ const high = singleCode(tokenRanges(atom.slice(i + 1, i + 1 + toLength), true));
380
+ if (low === null || high === null) return null;
381
+ members.push([low, high]);
382
+ i += 1 + toLength;
383
+ }
384
+
385
+ return negated ? complement(members) : members;
386
+ }
387
+
388
+ /** @param {[number, number][]} ranges @param {[number, number][]} other */
389
+ function rangesIntersect(ranges, other) {
390
+ return ranges.some(([low, high]) => other.some(([otherLow, otherHigh]) =>
391
+ low <= otherHigh && otherLow <= high));
392
+ }
393
+
187
394
  // Embedding a pattern in a larger regex drops the flags it carried, so the two
188
395
  // a source can carry on its own are rewritten into it
189
396
  //
190
397
  // @@ Replace with inline `(?i:…)` and `(?s:…)` modifiers once Node floor reaches 24
191
398
 
192
- const RE_ASCII_LETTER = /^[a-zA-Z]$/;
193
- const RE_HEX_PAIR = /^[0-9a-fA-F]{2}$/;
194
- const RE_HEX_QUAD = /^[0-9a-fA-F]{4}$/;
195
-
196
399
  /** @param {string} char @returns {boolean} Whether the character has a single-character counterpart */
197
400
  function foldsCase(char) {
198
401
  const lower = char.toLowerCase();
@@ -206,36 +409,10 @@ function isLower(char) {
206
409
  }
207
410
 
208
411
  /**
209
- * Length of the token at `index`, so that a multi-character escape is read whole
210
- * rather than leaving its tail to be mistaken for literals.
211
- * @param {string} source @param {number} index
412
+ * @param {string} source - Regex source, assumed syntactically valid
413
+ * @param {boolean} [nested] - Whether the pattern carries `v`
212
414
  */
213
- function tokenLength(source, index) {
214
- if (source[index] !== '\\') return 1;
215
- const next = source[index + 1];
216
- if (next === 'x' && RE_HEX_PAIR.test(source.slice(index + 2, index + 4))) return 4;
217
- if (next === 'u') {
218
- if (source[index + 2] === '{') {
219
- const close = source.indexOf('}', index + 3);
220
- if (close !== -1) return close - index + 1;
221
- }
222
- if (RE_HEX_QUAD.test(source.slice(index + 2, index + 6))) return 6;
223
- }
224
- if (next === 'c' && RE_ASCII_LETTER.test(source[index + 2] ?? '')) return 3;
225
- // A property name or a group name is syntax, not text to fold
226
- if ((next === 'p' || next === 'P') && source[index + 2] === '{') {
227
- const close = source.indexOf('}', index + 3);
228
- if (close !== -1) return close - index + 1;
229
- }
230
- if (next === 'k' && source[index + 2] === '<') {
231
- const close = source.indexOf('>', index + 3);
232
- if (close !== -1) return close - index + 1;
233
- }
234
- return 2;
235
- }
236
-
237
- /** @param {string} source - Regex source, assumed syntactically valid */
238
- function expandDotAll(source) {
415
+ function expandDotAll(source, nested = false) {
239
416
  let out = '';
240
417
  let i = 0;
241
418
  while (i < source.length) {
@@ -245,7 +422,7 @@ function expandDotAll(source) {
245
422
  out += source.slice(i, i + length);
246
423
  i += length;
247
424
  } else if (char === '[') {
248
- const end = skipCharacterClass(source, i);
425
+ const end = skipCharacterClass(source, i, nested);
249
426
  out += source.slice(i, end);
250
427
  i = end;
251
428
  } else if (char === '.') {
@@ -259,22 +436,25 @@ function expandDotAll(source) {
259
436
  return out;
260
437
  }
261
438
 
262
- /** @param {string} source - Regex source, assumed syntactically valid */
263
- function foldCase(source) {
439
+ /**
440
+ * @param {string} source - Regex source, assumed syntactically valid
441
+ * @param {boolean} [nested] - Whether the pattern carries `v`
442
+ */
443
+ function foldCase(source, nested = false) {
264
444
  let out = '';
265
445
  let i = 0;
266
- let inClass = false;
446
+ let open = 0;
267
447
 
268
448
  while (i < source.length) {
269
449
  const char = source[i] ?? '';
270
450
 
271
- if (!inClass) {
451
+ if (open === 0) {
272
452
  if (char === '\\') {
273
453
  const length = tokenLength(source, i);
274
454
  out += source.slice(i, i + length);
275
455
  i += length;
276
456
  } else if (char === '[') {
277
- inClass = true;
457
+ open = 1;
278
458
  out += char;
279
459
  i++;
280
460
  } else if (char === '(' && source.startsWith('(?<', i) &&
@@ -291,8 +471,15 @@ function foldCase(source) {
291
471
  continue;
292
472
  }
293
473
 
474
+ if (nested && char === '[') {
475
+ open++;
476
+ out += char;
477
+ i++;
478
+ continue;
479
+ }
480
+
294
481
  if (char === ']') {
295
- inClass = false;
482
+ open--;
296
483
  out += char;
297
484
  i++;
298
485
  continue;
@@ -324,6 +511,93 @@ function foldCase(source) {
324
511
  return out;
325
512
  }
326
513
 
514
+ /**
515
+ * Whether a source anchors anywhere, so that `m` would move where it matches
516
+ * @param {string} source @param {boolean} [nested] - Whether the pattern carries `v`
517
+ */
518
+ function hasAnchor(source, nested = false) {
519
+ let i = 0;
520
+ while (i < source.length) {
521
+ if (source[i] === '\\') i += tokenLength(source, i);
522
+ // Inside a class `^` negates and `$` is a member, so neither anchors there
523
+ else if (source[i] === '[') i = skipCharacterClass(source, i, nested);
524
+ else if (source[i] === '^' || source[i] === '$') return true;
525
+ else i++;
526
+ }
527
+ return false;
528
+ }
529
+
530
+ // A property escape or a code point escape, both of which read as literal text
531
+ // where the flag that gives them meaning is gone
532
+ const RE_UNICODE_ESCAPE = /\\[pP]\{|\\u\{/;
533
+
534
+ // The characters `u` folds by Unicode rules and a source without it does not:
535
+ // `/s/iu` matches `\u017F` and `/k/iu` matches `\u212A`, where neither does on its
536
+ // own. Characters past the BMP fold this way, too, and are caught as astral first.
537
+ const RE_UNICODE_FOLDING = /[\u004B\u0053\u006B\u0073\u00C5\u00DF\u00E5\u017F\u0398\u03A9\u03B8\u03C9\u03D1\u03F4\u1E9E\u1F80-\u1FAF\u1FB3\u1FBC\u1FC3\u1FCC\u1FF3\u1FFC\u2126\u212A\u212B]/;
538
+
539
+ /**
540
+ * Which flag changes what a source matches, so that embedding the source where the
541
+ * flag cannot follow would silently match something else. `u` and `v` only narrow
542
+ * what syntax is legal, so a source valid under either stays valid without it—the
543
+ * difference never surfaces as a syntax error, and this stands in for the one that
544
+ * would otherwise be raised.
545
+ * @param {RegExp} pattern
546
+ * @returns {'u' | 'v' | 'm' | null} The flag the source depends on, or `null` where
547
+ * every flag it carries would leave the source matching the same
548
+ */
549
+ function lostFlag(pattern) {
550
+ const { source, unicode, unicodeSets } = pattern;
551
+ // `m` moves where `^` and `$` match, and only matters where the source has one
552
+ if (pattern.multiline && hasAnchor(source, unicodeSets)) return 'm';
553
+ if (!unicode && !unicodeSets) return null;
554
+ const flag = unicodeSets ? 'v' : 'u';
555
+ if (RE_UNICODE_ESCAPE.test(source)) return flag;
556
+ // Case folding under `i` follows Unicode rules only while the flag is there. A
557
+ // character can be written literally or as an escape, so the source is read
558
+ // token by token rather than scanned for the characters themselves; `singleCode`
559
+ // leaves out `\d` and friends, which fold alike with the flag and without it.
560
+ if (pattern.ignoreCase) {
561
+ for (let i = 0; i < source.length;) {
562
+ const length = tokenLength(source, i);
563
+ const code = singleCode(tokenRanges(source.slice(i, i + length), true));
564
+ if (code !== null && RE_UNICODE_FOLDING.test(String.fromCodePoint(code))) return flag;
565
+ i += length;
566
+ }
567
+ }
568
+ // Past the BMP a character is one code point with the flag, two units without,
569
+ // which is what a quantifier or a class beside it would go on to read wrongly
570
+ for (const char of source) {
571
+ if ((char.codePointAt(0) ?? 0) > 0xFFFF) return flag;
572
+ }
573
+ if (!unicodeSets) return null;
574
+
575
+ // `v` alone lets a class nest, subtract, intersect, and hold whole strings
576
+ let i = 0;
577
+ while (i < source.length) {
578
+ if (source[i] === '\\') {
579
+ i += tokenLength(source, i);
580
+ continue;
581
+ }
582
+ if (source[i] !== '[') {
583
+ i++;
584
+ continue;
585
+ }
586
+ const end = skipCharacterClass(source, i, true);
587
+ for (let j = i + 1; j < end - 1;) {
588
+ if (source[j] === '\\') {
589
+ if (source.startsWith('\\q{', j)) return 'v';
590
+ j += tokenLength(source, j);
591
+ continue;
592
+ }
593
+ if (source[j] === '[' || source.startsWith('--', j) || source.startsWith('&&', j)) return 'v';
594
+ j++;
595
+ }
596
+ i = end;
597
+ }
598
+ return null;
599
+ }
600
+
327
601
  /**
328
602
  * A pattern’s source, rewritten to match the way its own flags make it match, for
329
603
  * embedding in a larger regex that cannot carry them. `i` and `s` fit into a
@@ -336,30 +610,77 @@ function foldCase(source) {
336
610
  */
337
611
  function embedSource(pattern) {
338
612
  let source = pattern.source;
339
- if (pattern.dotAll) source = expandDotAll(source);
340
- if (pattern.ignoreCase) source = foldCase(source);
613
+ if (pattern.dotAll) source = expandDotAll(source, pattern.unicodeSets);
614
+ if (pattern.ignoreCase) source = foldCase(source, pattern.unicodeSets);
341
615
  return source;
342
616
  }
343
617
 
618
+ /**
619
+ * @typedef {{text: string, ranges: [number, number][] | null}} Repeat - An unbounded
620
+ * repeat, as it reads and as the set of characters it matches
621
+ */
622
+
623
+ /**
624
+ * @param {Repeat[]} reachable - Repeats that could still sit adjacent to what comes next
625
+ * @param {Repeat} repeat
626
+ * @returns {boolean} Whether the two can consume the same character, and so split
627
+ * the same run of input between them
628
+ */
629
+ function splitsWith(reachable, repeat) {
630
+ return reachable.some(earlier => earlier.text === repeat.text ||
631
+ (!!repeat.ranges && !!earlier.ranges && rangesIntersect(repeat.ranges, earlier.ranges)));
632
+ }
633
+
634
+ /**
635
+ * @param {Repeat[]} into
636
+ * @param {Repeat[]} repeats
637
+ * @returns {void} Adds the repeats, keeping only the most recent ones the walk
638
+ * still compares against
639
+ */
640
+ function collectReachable(into, repeats) {
641
+ for (const repeat of repeats) {
642
+ into.push(repeat);
643
+ if (into.length > MAX_REACHABLE) into.shift();
644
+ }
645
+ }
646
+
344
647
  /**
345
648
  * Walk a regex source for the shapes whose backtracking blows up: an unlimited
346
649
  * quantifier over a group that itself contains a variable quantifier (`(a+)+`,
347
- * `(a?)+`) or alternates, and the same atom repeated unboundedly twice in a
348
- * row. A lone unlimited quantifier stays linear, so `[\s\S]*?` up to a literal
349
- * terminator passes.
650
+ * `(a?)+`) or alternates, and two unbounded repeats that can consume the same
651
+ * character with only atoms matching empty between them (`.*.*`, `[a]*a*`,
652
+ * `\w*\d*`, `a*b*a*`, `a*(b*)a*`, `(a*)a*`). A lone unlimited quantifier stays
653
+ * linear, so `[\s\S]*?` up to a literal terminator passes.
350
654
  * @param {string} source - Regex source, assumed syntactically valid
351
655
  * @param {number} [depth] - Group nesting level of this call
352
- * @returns {{risky: boolean, varies: boolean, alternates: boolean, deep: boolean}}
656
+ * @param {boolean} [nested] - Whether the pattern carries `v`
657
+ * @returns {{risky: boolean, varies: boolean, alternates: boolean, deep: boolean,
658
+ * empty: boolean, leading: Repeat[], trailing: Repeat[]}} What the walk found,
659
+ * with the repeats that reach the source’s start and end for a caller to compare
660
+ * against what stands beside it
353
661
  */
354
- function analyzeQuantifiers(source, depth = 0) {
355
- if (depth > MAX_PATTERN_DEPTH) return { risky: true, varies: true, alternates: true, deep: true };
662
+ function analyzeQuantifiers(source, depth = 0, nested = false) {
663
+ if (depth > MAX_PATTERN_DEPTH) return { risky: true, varies: true, alternates: true, deep: true, empty: true, leading: [], trailing: [] };
356
664
 
357
665
  let risky = false;
358
666
  let varies = false;
359
667
  let alternates = false;
360
668
  let deep = false;
361
- /** @type {{text: string, repeats: boolean} | null} */
362
- let previous = null;
669
+ // Whether every atom of the branch being read matches empty, and whether any
670
+ // branch before it did
671
+ let branchEmpty = true;
672
+ let anyBranchEmpty = false;
673
+ // The unbounded repeats that could still sit adjacent to what comes next, most
674
+ // recent last; an atom matching empty leaves the ones before it reachable
675
+ /** @type {Repeat[]} */
676
+ const reachable = [];
677
+ // The repeats a caller can reach from either end, gathered across branches
678
+ /** @type {Repeat[]} */
679
+ const leading = [];
680
+ /** @type {Repeat[]} */
681
+ const trailing = [];
682
+ /** @type {Repeat[]} */
683
+ let branchLeading = [];
363
684
  let i = 0;
364
685
 
365
686
  while (i < source.length) {
@@ -371,22 +692,27 @@ function analyzeQuantifiers(source, depth = 0) {
371
692
  if (char === '|') {
372
693
  // Alternatives are separate expressions, so nothing carries across
373
694
  alternates = true;
374
- previous = null;
695
+ anyBranchEmpty ||= branchEmpty;
696
+ branchEmpty = true;
697
+ collectReachable(leading, branchLeading);
698
+ collectReachable(trailing, reachable);
699
+ branchLeading = [];
700
+ reachable.length = 0;
375
701
  i++;
376
702
  continue;
377
703
  } else if (char === '\\') {
378
- i += 2;
704
+ i += tokenLength(source, i);
379
705
  } else if (char === '[') {
380
- i = skipCharacterClass(source, i);
706
+ i = skipCharacterClass(source, i, nested);
381
707
  } else if (char === '(') {
382
708
  let open = 1;
383
709
  i++;
384
710
  while (i < source.length && open > 0) {
385
711
  const inner = source[i];
386
712
  if (inner === '\\') {
387
- i += 2;
713
+ i += tokenLength(source, i);
388
714
  } else if (inner === '[') {
389
- i = skipCharacterClass(source, i);
715
+ i = skipCharacterClass(source, i, nested);
390
716
  } else {
391
717
  if (inner === '(') open++;
392
718
  else if (inner === ')') open--;
@@ -394,7 +720,7 @@ function analyzeQuantifiers(source, depth = 0) {
394
720
  }
395
721
  }
396
722
  // Drop the group prefix (`?:`, `?=`, `?<name>`, …) before reading the body
397
- group = analyzeQuantifiers(source.slice(start + 1, i - 1).replace(/^\?(?:[:=!]|<[=!]|<[^>]*>)/, ''), depth + 1);
723
+ group = analyzeQuantifiers(source.slice(start + 1, i - 1).replace(/^\?(?:[:=!]|<[=!]|<[^>]*>)/, ''), depth + 1, nested);
398
724
  } else {
399
725
  i++;
400
726
  }
@@ -410,35 +736,71 @@ function analyzeQuantifiers(source, depth = 0) {
410
736
  const exact = !!quantifier && quantifier[0][0] === '{' &&
411
737
  (upper === undefined || (upper !== '' && Number(upper) === Number(quantifier[1])));
412
738
  const variable = !!quantifier && !exact;
739
+ // An atom matches empty where it is zero-width, where its quantifier may
740
+ // repeat none, or where a group body matches empty however often it repeats—
741
+ // `(b*)`, carrying the quantifier inside the group rather than on it
742
+ const least = quantifier && (quantifier[0][0] === '+' ? 1
743
+ : quantifier[0][0] === '{' ? Number(quantifier[1]) : 0);
744
+ const body = RE_ZERO_WIDTH.test(atom) || (!!group && group.empty);
745
+ const nullable = body || (!!quantifier && least === 0);
413
746
  if (quantifier) i = RE_QUANTIFIER.lastIndex;
414
747
 
748
+ // A lookaround is atomic: It backtracks nothing, so what it holds neither
749
+ // reaches out of it nor is reached into
750
+ const opaque = RE_ZERO_WIDTH.test(atom);
415
751
  if (group) {
416
752
  if (group.risky || (repeats && (group.varies || group.alternates))) risky = true;
417
753
  if (group.varies) varies = true;
418
754
  // A group alternates whether the `|` sits at its top level or deeper
419
755
  if (group.alternates) alternates = true;
420
756
  if (group.deep) deep = true;
757
+ // A group is no wall: What it repeats at its start splits the same run as
758
+ // what stands unbounded before it
759
+ if (!opaque && group.leading.some(repeat => splitsWith(reachable, repeat))) risky = true;
421
760
  }
422
761
  if (variable) varies = true;
423
762
  const text = unwrapAtom(atom);
424
- if (repeats && previous?.repeats && previous.text === text) risky = true;
425
- previous = { text, repeats };
763
+ const ranges = atomRanges(text, nested);
764
+ if (repeats && splitsWith(reachable, { text, ranges })) risky = true;
765
+
766
+ // Whatever a caller could reach before this atom, it reaches this one, too
767
+ if (branchEmpty) {
768
+ if (repeats) collectReachable(branchLeading, [{ text, ranges }]);
769
+ if (group && !opaque) collectReachable(branchLeading, group.leading);
770
+ }
771
+ // Nothing reaches past an atom that has to consume something
772
+ if (!nullable) {
773
+ reachable.length = 0;
774
+ branchEmpty = false;
775
+ }
776
+ if (group && !opaque) collectReachable(reachable, group.trailing);
777
+ if (repeats) collectReachable(reachable, [{ text, ranges }]);
426
778
  }
427
779
 
428
- return { risky, varies, alternates, deep };
780
+ collectReachable(leading, branchLeading);
781
+ collectReachable(trailing, reachable);
782
+
783
+ return { risky, varies, alternates, deep, empty: anyBranchEmpty || branchEmpty, leading, trailing };
429
784
  }
430
785
 
431
786
  /**
432
- * @param {string} source - Regex source to judge
787
+ * @param {RegExp | string} pattern - Pattern to judge, or a bare source to read
788
+ * as it stands
433
789
  * @returns {string | null} What makes the pattern a backtracking risk, phrased
434
790
  * to follow the pattern itself, or `null` where it is none
435
791
  */
436
- function describeQuantifierRisk(source) {
437
- // A pattern too big to read is refused for that, not for a shape nobody saw
792
+ function describeQuantifierRisk(pattern) {
793
+ const source = typeof pattern === 'string' ? pattern : pattern.source;
794
+ // A pattern too big to read is refused for that, not for a shape nobody saw;
795
+ // its own length is what counts, not the length folding case inflates it to
438
796
  if (source.length > MAX_PATTERN_LENGTH) {
439
797
  return `runs past ${MAX_PATTERN_LENGTH.toLocaleString()} characters, too long to analyze for catastrophic backtracking—shorten it, or split it into several patterns`;
440
798
  }
441
- const analysis = analyzeQuantifiers(source);
799
+ // `i` and `s` change what the source matches, so they are written into it
800
+ // before the shapes are read
801
+ const analysis = typeof pattern === 'string'
802
+ ? analyzeQuantifiers(source)
803
+ : analyzeQuantifiers(embedSource(pattern), 0, pattern.unicodeSets);
442
804
  if (analysis.deep) {
443
805
  return `nests groups more than ${MAX_PATTERN_DEPTH.toLocaleString()} deep, too deep to analyze for catastrophic backtracking—flatten it, or split it into several patterns`;
444
806
  }
@@ -484,5 +846,6 @@ export {
484
846
  replaceAsync,
485
847
  parseRegExp,
486
848
  embedSource,
849
+ lostFlag,
487
850
  describeQuantifierRisk
488
851
  };