@ploaness/governance 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +13 -0
  3. package/dist/access-boundary.d.ts +41 -0
  4. package/dist/access-boundary.d.ts.map +1 -0
  5. package/dist/access-boundary.js +26 -0
  6. package/dist/access-boundary.js.map +1 -0
  7. package/dist/agent-references.d.ts +12 -0
  8. package/dist/agent-references.d.ts.map +1 -0
  9. package/dist/agent-references.js +53 -0
  10. package/dist/agent-references.js.map +1 -0
  11. package/dist/architecture-layers.d.ts +11 -0
  12. package/dist/architecture-layers.d.ts.map +1 -0
  13. package/dist/architecture-layers.js +47 -0
  14. package/dist/architecture-layers.js.map +1 -0
  15. package/dist/asset-policy.d.ts +138 -0
  16. package/dist/asset-policy.d.ts.map +1 -0
  17. package/dist/asset-policy.js +312 -0
  18. package/dist/asset-policy.js.map +1 -0
  19. package/dist/banned-typography.d.ts +13 -0
  20. package/dist/banned-typography.d.ts.map +1 -0
  21. package/dist/banned-typography.js +78 -0
  22. package/dist/banned-typography.js.map +1 -0
  23. package/dist/bundle-budget.d.ts +16 -0
  24. package/dist/bundle-budget.d.ts.map +1 -0
  25. package/dist/bundle-budget.js +37 -0
  26. package/dist/bundle-budget.js.map +1 -0
  27. package/dist/commit-message.d.ts +37 -0
  28. package/dist/commit-message.d.ts.map +1 -0
  29. package/dist/commit-message.js +144 -0
  30. package/dist/commit-message.js.map +1 -0
  31. package/dist/config-references.d.ts +17 -0
  32. package/dist/config-references.d.ts.map +1 -0
  33. package/dist/config-references.js +54 -0
  34. package/dist/config-references.js.map +1 -0
  35. package/dist/container-failure.d.ts +40 -0
  36. package/dist/container-failure.d.ts.map +1 -0
  37. package/dist/container-failure.js +154 -0
  38. package/dist/container-failure.js.map +1 -0
  39. package/dist/container-files.d.ts +36 -0
  40. package/dist/container-files.d.ts.map +1 -0
  41. package/dist/container-files.js +74 -0
  42. package/dist/container-files.js.map +1 -0
  43. package/dist/container-freshness.d.ts +102 -0
  44. package/dist/container-freshness.d.ts.map +1 -0
  45. package/dist/container-freshness.js +129 -0
  46. package/dist/container-freshness.js.map +1 -0
  47. package/dist/dependency-freshness.d.ts +129 -0
  48. package/dist/dependency-freshness.d.ts.map +1 -0
  49. package/dist/dependency-freshness.js +161 -0
  50. package/dist/dependency-freshness.js.map +1 -0
  51. package/dist/doc-blocks.d.ts +13 -0
  52. package/dist/doc-blocks.d.ts.map +1 -0
  53. package/dist/doc-blocks.js +75 -0
  54. package/dist/doc-blocks.js.map +1 -0
  55. package/dist/document-references.d.ts +44 -0
  56. package/dist/document-references.d.ts.map +1 -0
  57. package/dist/document-references.js +94 -0
  58. package/dist/document-references.js.map +1 -0
  59. package/dist/editorconfig-policy.d.ts +24 -0
  60. package/dist/editorconfig-policy.d.ts.map +1 -0
  61. package/dist/editorconfig-policy.js +106 -0
  62. package/dist/editorconfig-policy.js.map +1 -0
  63. package/dist/environment-files.d.ts +42 -0
  64. package/dist/environment-files.d.ts.map +1 -0
  65. package/dist/environment-files.js +76 -0
  66. package/dist/environment-files.js.map +1 -0
  67. package/dist/file-roles.d.ts +39 -0
  68. package/dist/file-roles.d.ts.map +1 -0
  69. package/dist/file-roles.js +82 -0
  70. package/dist/file-roles.js.map +1 -0
  71. package/dist/generated-denial.d.ts +42 -0
  72. package/dist/generated-denial.d.ts.map +1 -0
  73. package/dist/generated-denial.js +87 -0
  74. package/dist/generated-denial.js.map +1 -0
  75. package/dist/history-policy.d.ts +32 -0
  76. package/dist/history-policy.d.ts.map +1 -0
  77. package/dist/history-policy.js +50 -0
  78. package/dist/history-policy.js.map +1 -0
  79. package/dist/image-assets.d.ts +14 -0
  80. package/dist/image-assets.d.ts.map +1 -0
  81. package/dist/image-assets.js +205 -0
  82. package/dist/image-assets.js.map +1 -0
  83. package/dist/index.d.ts +49 -0
  84. package/dist/index.d.ts.map +1 -0
  85. package/dist/index.js +53 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/install-policy.d.ts +35 -0
  88. package/dist/install-policy.d.ts.map +1 -0
  89. package/dist/install-policy.js +61 -0
  90. package/dist/install-policy.js.map +1 -0
  91. package/dist/json-shapes.d.ts +86 -0
  92. package/dist/json-shapes.d.ts.map +1 -0
  93. package/dist/json-shapes.js +124 -0
  94. package/dist/json-shapes.js.map +1 -0
  95. package/dist/license-allowlist.d.ts +15 -0
  96. package/dist/license-allowlist.d.ts.map +1 -0
  97. package/dist/license-allowlist.js +157 -0
  98. package/dist/license-allowlist.js.map +1 -0
  99. package/dist/network-policy.d.ts +69 -0
  100. package/dist/network-policy.d.ts.map +1 -0
  101. package/dist/network-policy.js +173 -0
  102. package/dist/network-policy.js.map +1 -0
  103. package/dist/paired-assets.d.ts +34 -0
  104. package/dist/paired-assets.d.ts.map +1 -0
  105. package/dist/paired-assets.js +63 -0
  106. package/dist/paired-assets.js.map +1 -0
  107. package/dist/payload-access.d.ts +9 -0
  108. package/dist/payload-access.d.ts.map +1 -0
  109. package/dist/payload-access.js +168 -0
  110. package/dist/payload-access.js.map +1 -0
  111. package/dist/payload-policy.d.ts +18 -0
  112. package/dist/payload-policy.d.ts.map +1 -0
  113. package/dist/payload-policy.js +124 -0
  114. package/dist/payload-policy.js.map +1 -0
  115. package/dist/payload-source.d.ts +29 -0
  116. package/dist/payload-source.d.ts.map +1 -0
  117. package/dist/payload-source.js +84 -0
  118. package/dist/payload-source.js.map +1 -0
  119. package/dist/release-age.d.ts +23 -0
  120. package/dist/release-age.d.ts.map +1 -0
  121. package/dist/release-age.js +38 -0
  122. package/dist/release-age.js.map +1 -0
  123. package/dist/request-limits.d.ts +39 -0
  124. package/dist/request-limits.d.ts.map +1 -0
  125. package/dist/request-limits.js +57 -0
  126. package/dist/request-limits.js.map +1 -0
  127. package/dist/run-plan.d.ts +32 -0
  128. package/dist/run-plan.d.ts.map +1 -0
  129. package/dist/run-plan.js +43 -0
  130. package/dist/run-plan.js.map +1 -0
  131. package/dist/run-policy.d.ts +32 -0
  132. package/dist/run-policy.d.ts.map +1 -0
  133. package/dist/run-policy.js +37 -0
  134. package/dist/run-policy.js.map +1 -0
  135. package/dist/secret-policy.d.ts +13 -0
  136. package/dist/secret-policy.d.ts.map +1 -0
  137. package/dist/secret-policy.js +69 -0
  138. package/dist/secret-policy.js.map +1 -0
  139. package/dist/settings-layering.d.ts +45 -0
  140. package/dist/settings-layering.d.ts.map +1 -0
  141. package/dist/settings-layering.js +134 -0
  142. package/dist/settings-layering.js.map +1 -0
  143. package/dist/settings.d.ts +238 -0
  144. package/dist/settings.d.ts.map +1 -0
  145. package/dist/settings.js +378 -0
  146. package/dist/settings.js.map +1 -0
  147. package/dist/skill-manifest.d.ts +18 -0
  148. package/dist/skill-manifest.d.ts.map +1 -0
  149. package/dist/skill-manifest.js +125 -0
  150. package/dist/skill-manifest.js.map +1 -0
  151. package/dist/source-text.d.ts +83 -0
  152. package/dist/source-text.d.ts.map +1 -0
  153. package/dist/source-text.js +310 -0
  154. package/dist/source-text.js.map +1 -0
  155. package/dist/suppression-ceiling.d.ts +48 -0
  156. package/dist/suppression-ceiling.d.ts.map +1 -0
  157. package/dist/suppression-ceiling.js +115 -0
  158. package/dist/suppression-ceiling.js.map +1 -0
  159. package/dist/tailwind-tokens.d.ts +16 -0
  160. package/dist/tailwind-tokens.d.ts.map +1 -0
  161. package/dist/tailwind-tokens.js +70 -0
  162. package/dist/tailwind-tokens.js.map +1 -0
  163. package/dist/text-escapes.d.ts +7 -0
  164. package/dist/text-escapes.d.ts.map +1 -0
  165. package/dist/text-escapes.js +14 -0
  166. package/dist/text-escapes.js.map +1 -0
  167. package/dist/toolchain-pins.d.ts +42 -0
  168. package/dist/toolchain-pins.d.ts.map +1 -0
  169. package/dist/toolchain-pins.js +99 -0
  170. package/dist/toolchain-pins.js.map +1 -0
  171. package/dist/url-safety.d.ts +12 -0
  172. package/dist/url-safety.d.ts.map +1 -0
  173. package/dist/url-safety.js +58 -0
  174. package/dist/url-safety.js.map +1 -0
  175. package/dist/version-policy.d.ts +99 -0
  176. package/dist/version-policy.d.ts.map +1 -0
  177. package/dist/version-policy.js +306 -0
  178. package/dist/version-policy.js.map +1 -0
  179. package/dist/vulnerability-policy.d.ts +66 -0
  180. package/dist/vulnerability-policy.d.ts.map +1 -0
  181. package/dist/vulnerability-policy.js +106 -0
  182. package/dist/vulnerability-policy.js.map +1 -0
  183. package/dist/wiring-policy.d.ts +226 -0
  184. package/dist/wiring-policy.d.ts.map +1 -0
  185. package/dist/wiring-policy.js +458 -0
  186. package/dist/wiring-policy.js.map +1 -0
  187. package/dist/wiring-violation.d.ts +6 -0
  188. package/dist/wiring-violation.d.ts.map +1 -0
  189. package/dist/wiring-violation.js +8 -0
  190. package/dist/wiring-violation.js.map +1 -0
  191. package/dist/workspace-policy.d.ts +177 -0
  192. package/dist/workspace-policy.d.ts.map +1 -0
  193. package/dist/workspace-policy.js +228 -0
  194. package/dist/workspace-policy.js.map +1 -0
  195. package/dist/yaml-blocks.d.ts +43 -0
  196. package/dist/yaml-blocks.d.ts.map +1 -0
  197. package/dist/yaml-blocks.js +87 -0
  198. package/dist/yaml-blocks.js.map +1 -0
  199. package/package.json +41 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-text.d.ts","sourceRoot":"","sources":["../src/source-text.ts"],"names":[],"mappings":"AAkBA,wDAAwD;AACxD,eAAO,MAAM,cAAc,EAAE,MAAW,CAAA;AAExC,8FAA8F;AAC9F,eAAO,MAAM,SAAS,EAAE,MAAW,CAAA;AAEnC;;;;;GAKG;AACH,eAAO,MAAM,MAAM,GAAI,QAAQ,MAAM,EAAE,OAAO,MAAM,KAAG,MACZ,CAAA;AAqI3C,yEAAyE;AACzE,MAAM,WAAW,MAAM,CAAC,KAAK;IAC3B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CACvB;AAoED;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,GAAI,QAAQ,MAAM,KAAG,MAA8B,CAAA;AAE7E;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,YAAY,GAAI,QAAQ,MAAM,KAAG,MAA6B,CAAA;AAE3E,qGAAqG;AACrG,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AASD;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,GAAI,KAAK,EACjC,QAAQ,MAAM,EACd,OAAO,MAAM,EACb,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,KAAK,MAAM,CAAC,KAAK,CAAC,EACrD,SAAS,KAAK,KACb,KA0BF,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,MAAM,EAAE,MAAM,MAAM,KAAG,MAAM,GAAG,SAWzE,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GAAI,cAAc,MAAM,KAAG,MAgBlD,CAAA;AAEH;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,QAAQ,MAAM,EAAE,QAAQ,MAAM,KAAG,SAAS,MAAM,EAGzE,CAAA;AAKH;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GAAI,QAAQ,MAAM,EAAE,OAAO,MAAM,KAAG,SAAS,MAAM,EAY3E,CAAA"}
@@ -0,0 +1,310 @@
1
+ // Reading source text without parsing it: comment stripping, balanced-delimiter scanning, and the
2
+ // depth-aware key lookups that tell a literal's own key from a nested one.
3
+ //
4
+ // It is the generic half of what was `payload-policy.ts`, extracted when that file reached the size cap
5
+ // and then again when a second layer needed it. Three readers share it now: the Payload rules in
6
+ // `payload-source.ts`, and the JSONC reader in `json-shapes.ts` - a tsconfig legally carries comments,
7
+ // so the rule that judges one has to strip them the same way, and a second stripper is a second set of
8
+ // edge cases about string literals nobody would keep in step.
9
+ //
10
+ // The walk is deliberately conservative. A reader returns undefined wherever the answer is not
11
+ // statically decidable, because a false positive in a build-failing gate costs far more than a dynamic
12
+ // construct a reviewer would catch anyway.
13
+ import { escapeForRegex } from './text-escapes.js';
14
+ const OPENERS = new Set(['(', '[', '{']);
15
+ const CLOSERS = new Set([')', ']', '}']);
16
+ const QUOTES = new Set(["'", '"', '`']);
17
+ /** The offset `at` reads to reach the final element. */
18
+ export const LAST_CHARACTER = -1;
19
+ /** The value `indexOf` and this module's readers return when there is nothing to point at. */
20
+ export const NOT_FOUND = -1;
21
+ /**
22
+ * The one-based line a source offset falls on.
23
+ * @param source the file contents.
24
+ * @param index the offset.
25
+ * @returns the line number, counting from one.
26
+ */
27
+ export const lineOf = (source, index) => source.slice(0, index).split('\n').length;
28
+ // A `/` begins a regular-expression literal only where a value may start. Tracking that lets the comment
29
+ // scanner skip a literal such as /https:\/\// without mistaking its escaped slashes for a comment.
30
+ const VALUE_POSITION = new Set([
31
+ '(',
32
+ ',',
33
+ '=',
34
+ ':',
35
+ '[',
36
+ '!',
37
+ '&',
38
+ '|',
39
+ '?',
40
+ '{',
41
+ '}',
42
+ ';',
43
+ '+',
44
+ '-',
45
+ '*',
46
+ '%',
47
+ '<',
48
+ '>',
49
+ '~',
50
+ '^',
51
+ ]);
52
+ const lastMeaningful = (text) => text.trimEnd().at(LAST_CHARACTER) ?? '';
53
+ // Each scanner below answers one question: where does the construct that starts at `index` end? Keeping
54
+ // them apart is what lets stripComments read as the dispatch it is rather than as four interleaved
55
+ // state machines sharing one cursor.
56
+ // A replacement FUNCTION rather than a replacement string: a string replacement reads `$&` and its
57
+ // siblings as syntax, and the character to fill with is a parameter here rather than a literal.
58
+ const fill = (text, character) => text.replaceAll(/[^\n]/g, () => character);
59
+ const blank = (text) => fill(text, ' ');
60
+ // A masked string becomes a filler that is NOT whitespace, unlike an erased comment. A caller reads the
61
+ // mask for syntax, and a string blanked to spaces stops being a token: `["a", "b"]` would mask to a
62
+ // comma followed by whitespace and a bracket, which is precisely the trailing comma such a caller
63
+ // looks for. The character itself carries no meaning beyond being neither space nor delimiter.
64
+ const STRING_FILLER = 'x';
65
+ // `/*`, `*/`, and `//` are each two characters wide, and a backslash escape is the character after it.
66
+ const DELIMITER_WIDTH = 2;
67
+ const BLOCK_COMMENT_END = '*/';
68
+ const endOfLineComment = (source, index) => {
69
+ const end = source.indexOf('\n', index);
70
+ return end === NOT_FOUND ? source.length : end;
71
+ };
72
+ const endOfBlockComment = (source, index) => {
73
+ const end = source.indexOf(BLOCK_COMMENT_END, index + DELIMITER_WIDTH);
74
+ return end === NOT_FOUND ? source.length : end + DELIMITER_WIDTH;
75
+ };
76
+ // Iterative rather than recursive, for the reason the main walk below is: a single string literal can be
77
+ // a whole inlined data URI, and V8 does not eliminate the tail call that a per-character recursion
78
+ // leaves on the stack.
79
+ const scanStringLiteral = (source, quote, start) => {
80
+ // eslint-disable-next-line functional/no-let -- one cursor confined to this walk
81
+ let cursor = start;
82
+ while (cursor < source.length) {
83
+ const inner = source[cursor] ?? '';
84
+ if (inner === '\\') {
85
+ cursor += DELIMITER_WIDTH;
86
+ continue;
87
+ }
88
+ if (inner === quote) {
89
+ return cursor + 1;
90
+ }
91
+ cursor += 1;
92
+ }
93
+ return cursor;
94
+ };
95
+ const endOfStringLiteral = (source, index) => {
96
+ const quote = source[index] ?? '';
97
+ return scanStringLiteral(source, quote, index + 1);
98
+ };
99
+ // A regex literal ends at the first unescaped slash outside a character class, and never spans a line.
100
+ // Its end depends on escapes, newlines, and character-class nesting at once; splitting the three apart
101
+ // would hide the single cursor they share behind three functions that each need the others' state.
102
+ const isInClassAfter = (character, isInClass) => {
103
+ if (character === '[') {
104
+ return true;
105
+ }
106
+ return character === ']' ? false : isInClass;
107
+ };
108
+ // One character of the walk, so the loop below carries the cursor and this carries the decisions. A
109
+ // newline ends the literal without consuming it (a regex never spans a line); an unescaped slash
110
+ // outside a character class ends it and is part of it.
111
+ const afterRegexCharacter = (character, cursor) => {
112
+ if (character === '\\') {
113
+ return { ...cursor, index: cursor.index + DELIMITER_WIDTH };
114
+ }
115
+ if (character === '\n') {
116
+ return { ...cursor, end: cursor.index };
117
+ }
118
+ return character === '/' && !cursor.isInClass
119
+ ? { ...cursor, end: cursor.index + 1 }
120
+ : {
121
+ index: cursor.index + 1,
122
+ isInClass: isInClassAfter(character, cursor.isInClass),
123
+ end: undefined,
124
+ };
125
+ };
126
+ const scanRegexLiteral = (source, start) => {
127
+ // eslint-disable-next-line functional/no-let -- one cursor confined to this walk
128
+ let cursor = { index: start, isInClass: false, end: undefined };
129
+ while (cursor.index < source.length && cursor.end === undefined) {
130
+ cursor = afterRegexCharacter(source[cursor.index] ?? '', cursor);
131
+ }
132
+ return cursor.end ?? cursor.index;
133
+ };
134
+ const endOfRegexLiteral = (source, index) => scanRegexLiteral(source, index + 1);
135
+ // A line or block comment, if one opens at `index`. Split out of the construct dispatch below so each
136
+ // reads as the single question it asks.
137
+ const commentAt = (source, index) => {
138
+ if (source[index] !== '/') {
139
+ return undefined;
140
+ }
141
+ const next = source[index + 1] ?? '';
142
+ if (next === '/') {
143
+ return { stop: endOfLineComment(source, index), erased: true };
144
+ }
145
+ return next === '*' ? { stop: endOfBlockComment(source, index), erased: true } : undefined;
146
+ };
147
+ // A string literal is kept because its contents are the only place a banned construct can legitimately
148
+ // appear as data. Everything else here is erased, so prose naming a construct is never read as one.
149
+ const constructAt = (source, index, output) => {
150
+ const comment = commentAt(source, index);
151
+ if (comment !== undefined) {
152
+ return comment;
153
+ }
154
+ const character = source[index] ?? '';
155
+ if (QUOTES.has(character)) {
156
+ return { stop: endOfStringLiteral(source, index), erased: false };
157
+ }
158
+ if (character === '/' && VALUE_POSITION.has(lastMeaningful(output))) {
159
+ return { stop: endOfRegexLiteral(source, index), erased: true };
160
+ }
161
+ return undefined;
162
+ };
163
+ // One walk, two callers. A string is the only construct the two disagree about, so the difference is a
164
+ // parameter rather than a second scanner - which is what this module's own header says it exists to
165
+ // avoid, and what a second set of edge cases about where a string ends would cost.
166
+ const replacementFor = (text, skipped, isStringMasked) => {
167
+ if (skipped.erased) {
168
+ return blank(text);
169
+ }
170
+ return isStringMasked ? fill(text, STRING_FILLER) : text;
171
+ };
172
+ const strip = (source, isStringMasked) => {
173
+ /* eslint-disable functional/no-let -- a whole source file is walked here, so recursion would risk
174
+ the stack; the cursor and the output it builds are confined to this loop and escape as a value */
175
+ let output = '';
176
+ let index = 0;
177
+ /* eslint-enable functional/no-let -- the walk above is the only place this file mutates */
178
+ while (index < source.length) {
179
+ const skipped = constructAt(source, index, output);
180
+ if (skipped === undefined) {
181
+ output += source[index] ?? '';
182
+ index += 1;
183
+ continue;
184
+ }
185
+ const text = source.slice(index, skipped.stop);
186
+ output += replacementFor(text, skipped, isStringMasked);
187
+ index = skipped.stop;
188
+ }
189
+ return output;
190
+ };
191
+ /**
192
+ * Blank every comment and regular-expression literal to spaces, leaving newlines intact so reported line
193
+ * numbers still point at real source positions. Without this, prose that merely names a banned construct
194
+ * would be reported as the construct itself, which is the difference between a gate that is trusted and
195
+ * one that is worked around.
196
+ * @param source the file contents.
197
+ * @returns the source with comments and regex literals replaced by spaces of equal length.
198
+ */
199
+ export const stripComments = (source) => strip(source, false);
200
+ /**
201
+ * The source with every string literal replaced by filler of equal length, on top of what
202
+ * {@link stripComments} erases.
203
+ *
204
+ * A mask, for a caller that must locate syntax without reading inside a string. Offsets still line up
205
+ * with the source, so a position found here names the same character there, and a masked string is
206
+ * still a token rather than a gap. `parseJsonc` is that caller: it removed trailing commas from the
207
+ * document itself, so a comma sitting inside a string before a closing bracket - as a brace-expansion
208
+ * glob ending in one carries - was deleted from the parsed value, and nothing said so.
209
+ * @param source the file contents.
210
+ * @returns the source with comments erased, strings filled, and newlines intact.
211
+ */
212
+ export const maskLiterals = (source) => strip(source, true);
213
+ const depthAfter = (character, depth) => {
214
+ if (OPENERS.has(character)) {
215
+ return depth + 1;
216
+ }
217
+ return CLOSERS.has(character) ? depth - 1 : depth;
218
+ };
219
+ /**
220
+ * Walk `source` from `start`, tracking string literals and bracket nesting so that a bracket or a quote
221
+ * inside a string cannot unbalance the scan. Every reader below needs exactly this bookkeeping and they
222
+ * differ only in what they do with each character, so it lives here once rather than in each of them.
223
+ * @param source the text to walk.
224
+ * @param start the index to begin at.
225
+ * @param fold called for every character outside a string literal; return stop to end the walk.
226
+ * @param initial the state the fold begins with.
227
+ */
228
+ export const scanDelimited = (source, start, fold, initial) => {
229
+ // One cursor carries depth across a character walk. Every split of this loop has to pass that cursor
230
+ // back and forth, which is harder to review than the loop, and this is the code the unbounded-call
231
+ // rules are decided by.
232
+ // eslint-disable-next-line functional/no-let -- one cursor shared by the whole walk
233
+ let cursor = {
234
+ index: start,
235
+ depth: 0,
236
+ state: initial,
237
+ };
238
+ while (cursor.index < source.length) {
239
+ const character = source[cursor.index] ?? '';
240
+ // A string literal is jumped over whole rather than tracked character by character, so a bracket or
241
+ // a quote inside it can never reach the depth count.
242
+ if (QUOTES.has(character)) {
243
+ cursor = { ...cursor, index: endOfStringLiteral(source, cursor.index) };
244
+ continue;
245
+ }
246
+ const depth = depthAfter(character, cursor.depth);
247
+ const folded = fold(cursor.state, { character, index: cursor.index, depth });
248
+ if (folded.stop) {
249
+ return folded.state;
250
+ }
251
+ cursor = { index: cursor.index + 1, depth, state: folded.state };
252
+ }
253
+ return cursor.state;
254
+ };
255
+ /**
256
+ * Read the text between the parenthesis at `open` and its match.
257
+ * @param source the file contents.
258
+ * @param open the index of the opening parenthesis.
259
+ * @returns the argument text, or undefined when the call is unterminated (a parse error another gate reports).
260
+ */
261
+ export const balancedArguments = (source, open) => {
262
+ const close = scanDelimited(source, open, (found, step) => CLOSERS.has(step.character) && step.depth === 0
263
+ ? { state: step.index, stop: true }
264
+ : { state: found, stop: false }, undefined);
265
+ return close === undefined ? undefined : source.slice(open + 1, close);
266
+ };
267
+ /**
268
+ * Reduce an argument list to the characters sitting directly inside its outermost object literal, so a
269
+ * key nested in a `where` clause is not mistaken for a bound declared on the call itself.
270
+ * @param argumentText the text between a call's parentheses.
271
+ * @returns the depth-one characters, with nested structures elided.
272
+ */
273
+ export const topLevelSlice = (argumentText) => scanDelimited(argumentText, 0, (collected, step) => {
274
+ if (OPENERS.has(step.character)) {
275
+ // The outermost opener collapses to one space so its keys cannot run into the previous token;
276
+ // a nested one contributes nothing, which is what elides the structure beneath it.
277
+ return { state: collected + (step.depth === 1 ? ' ' : ''), stop: false };
278
+ }
279
+ if (!CLOSERS.has(step.character) && step.depth === 1) {
280
+ return { state: collected + step.character, stop: false };
281
+ }
282
+ return { state: collected, stop: false };
283
+ }, '');
284
+ /**
285
+ * Every offset at which `needle` occurs literally.
286
+ * @param source the text to search.
287
+ * @param needle the literal to find; its punctuation is not read as pattern syntax.
288
+ * @returns the offsets, in reading order.
289
+ */
290
+ export const occurrences = (source, needle) => [...source.matchAll(new RegExp(escapeForRegex(needle), 'g'))].map((match) => match.index);
291
+ // A key opens the literal or follows a comma, which is what keeps a colon inside a value out of it.
292
+ const TOP_LEVEL_KEY = /(?:^|[{,])\s*([a-z_$][\w$]*)\s*:/gi;
293
+ /**
294
+ * The keys sitting directly inside the object literal that opens after `index`.
295
+ * @param source the text to read.
296
+ * @param index where to look for the opening brace.
297
+ * @returns the depth-one key names, with every nested structure elided.
298
+ */
299
+ export const topLevelKeys = (source, index) => {
300
+ const open = source.indexOf('{', index);
301
+ if (open === NOT_FOUND) {
302
+ return [];
303
+ }
304
+ const body = balancedArguments(source, open);
305
+ if (body === undefined) {
306
+ return [];
307
+ }
308
+ return [...topLevelSlice(`{${body}}`).matchAll(TOP_LEVEL_KEY)].map((match) => match[1] ?? '');
309
+ };
310
+ //# sourceMappingURL=source-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-text.js","sourceRoot":"","sources":["../src/source-text.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /** Suppressions permitted before any code is written, so a greenfield project is not born at zero. */
2
+ export declare const BASE_ALLOWANCE: number;
3
+ /** Source lines that earn one further suppression. */
4
+ export declare const LINES_PER_SUPPRESSION: number;
5
+ /** One suppression comment, located so an over-budget project is told which ones to reconsider. */
6
+ export interface SuppressionSite {
7
+ readonly file: string;
8
+ readonly line: number;
9
+ readonly token: string;
10
+ }
11
+ /** What the ceiling rule decided, including the numbers every run reports. */
12
+ export interface SuppressionReport {
13
+ readonly count: number;
14
+ readonly ceiling: number;
15
+ readonly remaining: number;
16
+ readonly sourceLines: number;
17
+ readonly withinCeiling: boolean;
18
+ readonly sites: readonly SuppressionSite[];
19
+ }
20
+ /**
21
+ * Locate every suppression comment in one file.
22
+ * @param file the path reported alongside each site.
23
+ * @param text the file's content.
24
+ * @returns one site per suppression, in line order.
25
+ */
26
+ export declare const findSuppressions: (file: string, text: string) => readonly SuppressionSite[];
27
+ /**
28
+ * Count the lines that the ceiling is measured against.
29
+ * @param text a source file's content.
30
+ * @returns its non-blank line count.
31
+ */
32
+ export declare const countSourceLines: (text: string) => number;
33
+ /**
34
+ * The permitted suppression count for a body of code.
35
+ * @param sourceLines non-blank lines of first-party source.
36
+ * @param declaredMaximum a project's own stricter cap, or undefined when it declares none.
37
+ * @returns the ceiling, which a declared maximum may lower and can never raise.
38
+ */
39
+ export declare const suppressionCeiling: (sourceLines: number, declaredMaximum: number | undefined) => number;
40
+ /**
41
+ * Judge a repository's suppressions against its ceiling.
42
+ * @param sites every suppression found.
43
+ * @param sourceLines non-blank lines of first-party source.
44
+ * @param declaredMaximum a project's own stricter cap, or undefined.
45
+ * @returns the verdict and the numbers every verification run reports.
46
+ */
47
+ export declare const judgeSuppressions: (sites: readonly SuppressionSite[], sourceLines: number, declaredMaximum: number | undefined) => SuppressionReport;
48
+ //# sourceMappingURL=suppression-ceiling.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suppression-ceiling.d.ts","sourceRoot":"","sources":["../src/suppression-ceiling.ts"],"names":[],"mappings":"AAoDA,sGAAsG;AACtG,eAAO,MAAM,cAAc,EAAE,MAAU,CAAA;AAEvC,sDAAsD;AACtD,eAAO,MAAM,qBAAqB,EAAE,MAAY,CAAA;AAEhD,mGAAmG;AACnG,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAED,8EAA8E;AAC9E,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;IAC/B,QAAQ,CAAC,KAAK,EAAE,SAAS,eAAe,EAAE,CAAA;CAC3C;AA2BD;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,EAAE,MAAM,MAAM,KAAG,SAAS,eAAe,EAIlF,CAAA;AAEJ;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,KAAG,MACmC,CAAA;AAEnF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAC7B,aAAa,MAAM,EACnB,iBAAiB,MAAM,GAAG,SAAS,KAClC,MAGF,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAC5B,OAAO,SAAS,eAAe,EAAE,EACjC,aAAa,MAAM,EACnB,iBAAiB,MAAM,GAAG,SAAS,KAClC,iBAUF,CAAA"}
@@ -0,0 +1,115 @@
1
+ // The suppression ceiling.
2
+ //
3
+ // A justification never stops a suppression from being added: whoever adds one writes it. What stops
4
+ // one is a budget, because a full budget makes the next suppression cost the removal of an existing
5
+ // one. So the ceiling is the mechanism, and the justification rules that analyzers already enforce are
6
+ // the complement rather than the substitute.
7
+ //
8
+ // The ceiling is a density against the size of the code rather than a bare count, so it neither
9
+ // strangles a small repository nor loosens as a large one grows. A project may declare a STRICTER
10
+ // ceiling and never a looser one: a setting that raised it would be the bypass that makes the whole
11
+ // mechanism decorative, and zero must be expressible, since "no suppression is permitted" is a
12
+ // position the standard names explicitly.
13
+ //
14
+ // The search tokens are assembled from fragments rather than written whole. This module would otherwise
15
+ // report itself, which is the same self-reference the typography rule solves by naming code points.
16
+ const DISABLE = 'disable';
17
+ const IGNORE = 'ignore';
18
+ const ALLOW = 'allow';
19
+ // One entry per suppression form. `eslint-enable`, `biome-ignore-end`, and `stylelint-enable` close a
20
+ // block that its opener already counted, so counting them would double every block suppression.
21
+ //
22
+ // `@ts-nocheck` was missing, and it is the broadest suppression available: it turns the type checker off
23
+ // for a whole file rather than for one line. `ban-ts-comment` refuses it wherever ESLint reads, so it
24
+ // was never free - but a file ESLint's config ignores escaped both, and the ceiling is what makes the
25
+ // next suppression cost the removal of an existing one.
26
+ //
27
+ // The three coverage forms belong here for a sharper reason than the rest. Every other suppression
28
+ // silences a finding; a coverage directive deletes the measurement, so the per-file floor is met by
29
+ // dropping the line out of the report rather than by writing the test that would cover it. That was the
30
+ // cheapest way in a governed repository to make a floor read as satisfied.
31
+ const SUPPRESSION_TOKENS = [
32
+ `eslint-${DISABLE}`,
33
+ `biome-${IGNORE}`,
34
+ `stylelint-${DISABLE}`,
35
+ `@ts-expect-error`,
36
+ `@ts-${IGNORE}`,
37
+ `@ts-nocheck`,
38
+ `v8 ${IGNORE}`,
39
+ `c8 ${IGNORE}`,
40
+ `istanbul ${IGNORE}`,
41
+ ];
42
+ const CLOSING_TOKENS = [
43
+ `eslint-enable`,
44
+ `biome-${IGNORE}-end`,
45
+ `stylelint-enable`,
46
+ `v8 ${IGNORE} stop`,
47
+ `c8 ${IGNORE} stop`,
48
+ ];
49
+ /** Suppressions permitted before any code is written, so a greenfield project is not born at zero. */
50
+ export const BASE_ALLOWANCE = 2;
51
+ /** Source lines that earn one further suppression. */
52
+ export const LINES_PER_SUPPRESSION = 500;
53
+ // A suppression token must open the comment that carries it. Prose ABOUT suppressions mentions the same
54
+ // words - a config comment reading "every eslint-disable needs a reason" is documentation, not a
55
+ // suppression - and counting those would charge a repository for explaining its own policy.
56
+ const COMMENT_OPENER = String.raw `(?:\/\/|\/\*|^\s*\*|^\s*#)\s*`;
57
+ const opensWith = (line, token) => new RegExp(`${COMMENT_OPENER}${token}`).test(line);
58
+ // The one suppression form that is not a comment convention. gitleaks reads a line for this literal and
59
+ // skips the line that carries it, whatever syntax the surrounding file comments in, so anchoring it to a
60
+ // comment opener the way the others are anchored would leave every non-C-style file free. A governed
61
+ // repository records a committed fake credential in the declared secret allowlist; this is the way
62
+ // around that, and it costs a suppression like any other.
63
+ const BARE_TOKENS = [`gitleaks:${ALLOW}`];
64
+ const tokenOnLine = (line) => {
65
+ if (CLOSING_TOKENS.some((closing) => opensWith(line, closing))) {
66
+ return undefined;
67
+ }
68
+ return (SUPPRESSION_TOKENS.find((token) => opensWith(line, token)) ??
69
+ BARE_TOKENS.find((token) => line.includes(token)));
70
+ };
71
+ /**
72
+ * Locate every suppression comment in one file.
73
+ * @param file the path reported alongside each site.
74
+ * @param text the file's content.
75
+ * @returns one site per suppression, in line order.
76
+ */
77
+ export const findSuppressions = (file, text) => text.split('\n').flatMap((line, index) => {
78
+ const token = tokenOnLine(line);
79
+ return token === undefined ? [] : [{ file, line: index + 1, token }];
80
+ });
81
+ /**
82
+ * Count the lines that the ceiling is measured against.
83
+ * @param text a source file's content.
84
+ * @returns its non-blank line count.
85
+ */
86
+ export const countSourceLines = (text) => text.split('\n').filter((line) => line.trim().length > 0).length;
87
+ /**
88
+ * The permitted suppression count for a body of code.
89
+ * @param sourceLines non-blank lines of first-party source.
90
+ * @param declaredMaximum a project's own stricter cap, or undefined when it declares none.
91
+ * @returns the ceiling, which a declared maximum may lower and can never raise.
92
+ */
93
+ export const suppressionCeiling = (sourceLines, declaredMaximum) => {
94
+ const earned = BASE_ALLOWANCE + Math.floor(sourceLines / LINES_PER_SUPPRESSION);
95
+ return declaredMaximum === undefined ? earned : Math.min(earned, declaredMaximum);
96
+ };
97
+ /**
98
+ * Judge a repository's suppressions against its ceiling.
99
+ * @param sites every suppression found.
100
+ * @param sourceLines non-blank lines of first-party source.
101
+ * @param declaredMaximum a project's own stricter cap, or undefined.
102
+ * @returns the verdict and the numbers every verification run reports.
103
+ */
104
+ export const judgeSuppressions = (sites, sourceLines, declaredMaximum) => {
105
+ const ceiling = suppressionCeiling(sourceLines, declaredMaximum);
106
+ return {
107
+ count: sites.length,
108
+ ceiling,
109
+ remaining: Math.max(0, ceiling - sites.length),
110
+ sourceLines,
111
+ withinCeiling: sites.length <= ceiling,
112
+ sites,
113
+ };
114
+ };
115
+ //# sourceMappingURL=suppression-ceiling.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suppression-ceiling.js","sourceRoot":"","sources":["../src/suppression-ceiling.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /** One arbitrary-Tailwind-value occurrence: where it is, and the token that caused it. */
2
+ export interface ArbitraryValueViolation {
3
+ /** 1-based line. */
4
+ readonly line: number;
5
+ /** 1-based column. */
6
+ readonly column: number;
7
+ /** The offending token, as written. */
8
+ readonly value: string;
9
+ }
10
+ /**
11
+ * Find every arbitrary Tailwind value in one file's text.
12
+ * @param content the file's text.
13
+ * @returns the violations, each with a 1-based line and column and the offending token.
14
+ */
15
+ export declare const findArbitraryValues: (content: string) => readonly ArbitraryValueViolation[];
16
+ //# sourceMappingURL=tailwind-tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tailwind-tokens.d.ts","sourceRoot":"","sources":["../src/tailwind-tokens.ts"],"names":[],"mappings":"AAkBA,0FAA0F;AAC1F,MAAM,WAAW,uBAAuB;IACtC,oBAAoB;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,sBAAsB;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,uCAAuC;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAgDD;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAAI,SAAS,MAAM,KAAG,SAAS,uBAAuB,EAoBrF,CAAA"}
@@ -0,0 +1,70 @@
1
+ // The token-bound rule: every colour, size, spacing, radius and motion value a Tailwind project
2
+ // renders must come from the theme, applied through a utility class.
3
+ //
4
+ // An arbitrary value - `bg-[#0a7]`, `p-[13px]`, `translate-y-[2px]` - hardcodes the number instead.
5
+ // Nothing downstream can see that it happened: the page renders, the build passes, and the design
6
+ // system quietly stops being the single source of the value. That is the defect this rule names, and
7
+ // it is a rule about Tailwind rather than about any one project, which is why it is here rather than
8
+ // in a script a project owns and can edit.
9
+ //
10
+ // The match is deliberately narrow, because the two things it must not confuse look alike:
11
+ //
12
+ // - An arbitrary VALUE is `<utility>-[<value>]` and IS a finding. The `-` before the bracket is what
13
+ // separates it from TypeScript index access (`medalChip[result.medal]`), which has no dash.
14
+ // - An arbitrary VARIANT is `data-[state=open]:...` or `[&>tr]:...` and is NOT. A variant is a
15
+ // selector prefix rather than a value, and is always followed by `:`. The negative lookahead below
16
+ // is what keeps it out - and is the reason this is a rule in TypeScript rather than a GritQL
17
+ // plugin, whose Rust regex engine has no lookahead to express it with.
18
+ // A utility with a bracketed value, not immediately followed by `:` (which would make it a variant).
19
+ const ARBITRARY_VALUE = /-\[[^\]]+\](?!:)/g;
20
+ // Utilities whose bracket takes something OTHER than a themeable value.
21
+ //
22
+ // `transition-[color,box-shadow]` names which properties animate and `will-change-[margin-top]` names
23
+ // which one is about to. `flex-[1_0_auto]` is the CSS `flex` shorthand - a grow/shrink/basis triple
24
+ // describing how a box behaves, not a colour, size, spacing, radius or motion value. Tailwind v4 has
25
+ // namespaces for every kind of value this rule governs and none for any of these, so reporting them
26
+ // asked for a token that cannot exist, and the only way out was a suppression: a real allowance spent
27
+ // on a false finding.
28
+ //
29
+ // `flex-[1]` is NOT excused by this. It is still reported, because Tailwind ships `flex-1` for it - the
30
+ // finding there is an arbitrary value written where a utility already exists, which is the defect.
31
+ // Only the shorthand form, which no utility and no token can express, is out of scope.
32
+ const PROPERTY_UTILITIES = ['transition', 'will-change'];
33
+ // The `flex` shorthand, told apart from `flex-[1]` by carrying more than one component. Written as a
34
+ // separate test rather than as another entry above, because `flex` is not a property utility: the
35
+ // bracket holds values, there is simply no namespace any of them could come from.
36
+ const FLEX_SHORTHAND = /^-\[[^\s\]_]*[\s_][^\]]*\]$/;
37
+ const carriesFlexShorthand = (lineText, index, value) => lineText.slice(0, index).endsWith('flex') && FLEX_SHORTHAND.test(value);
38
+ const carriesProperty = (lineText, index) => PROPERTY_UTILITIES.some((utility) => lineText.slice(0, index).endsWith(utility));
39
+ // A bracketed value that COMPUTES rather than states one is the opposite of a hardcoded value.
40
+ //
41
+ // `var(--x)` is how a value computed at render - a width from a prop, a gap from a CMS field - reaches a
42
+ // utility class, and a static theme cannot hold a per-instance value.
43
+ //
44
+ // A calculation is judged by what it computes OVER, which is where this rule previously drew the line in
45
+ // the wrong place. `calc(100px_+_2rem)` is arithmetic on two lengths that both belong in the theme, and
46
+ // is still a finding. `calc(100%_-_1px)` is not: a percentage resolves against whatever contains the
47
+ // element, so the number it produces is not knowable where a token would have to be written - the same
48
+ // reason `var(--x)` is excused, reached by a different route. The same holds for a viewport unit.
49
+ const CUSTOM_PROPERTY = /var\(\s*--/;
50
+ // A length that only means something once the element has a container or a viewport.
51
+ const CONTEXT_RELATIVE = /\d(?:%|v(?:w|h|min|max)\b)/;
52
+ const carriesComputedValue = (value) => CUSTOM_PROPERTY.test(value) || (value.includes('calc(') && CONTEXT_RELATIVE.test(value));
53
+ /**
54
+ * Find every arbitrary Tailwind value in one file's text.
55
+ * @param content the file's text.
56
+ * @returns the violations, each with a 1-based line and column and the offending token.
57
+ */
58
+ export const findArbitraryValues = (content) => {
59
+ const lines = content.split('\n');
60
+ return lines.flatMap((lineText, index) => [...lineText.matchAll(ARBITRARY_VALUE)]
61
+ .filter((match) => !(carriesProperty(lineText, match.index) ||
62
+ carriesComputedValue(match[0]) ||
63
+ carriesFlexShorthand(lineText, match.index, match[0])))
64
+ .map((match) => ({
65
+ line: index + 1,
66
+ column: match.index + 1,
67
+ value: match[0],
68
+ })));
69
+ };
70
+ //# sourceMappingURL=tailwind-tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tailwind-tokens.js","sourceRoot":"","sources":["../src/tailwind-tokens.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,qEAAqE;AACrE,EAAE;AACF,oGAAoG;AACpG,kGAAkG;AAClG,qGAAqG;AACrG,qGAAqG;AACrG,2CAA2C;AAC3C,EAAE;AACF,2FAA2F;AAC3F,EAAE;AACF,uGAAuG;AACvG,gGAAgG;AAChG,iGAAiG;AACjG,uGAAuG;AACvG,iGAAiG;AACjG,2EAA2E;AAY3E,qGAAqG;AACrG,MAAM,eAAe,GAAW,mBAAmB,CAAA;AAEnD,wEAAwE;AACxE,EAAE;AACF,sGAAsG;AACtG,oGAAoG;AACpG,qGAAqG;AACrG,oGAAoG;AACpG,sGAAsG;AACtG,sBAAsB;AACtB,EAAE;AACF,wGAAwG;AACxG,mGAAmG;AACnG,uFAAuF;AACvF,MAAM,kBAAkB,GAAsB,CAAC,YAAY,EAAE,aAAa,CAAC,CAAA;AAE3E,qGAAqG;AACrG,kGAAkG;AAClG,kFAAkF;AAClF,MAAM,cAAc,GAAW,6BAA6B,CAAA;AAE5D,MAAM,oBAAoB,GAAG,CAAC,QAAgB,EAAE,KAAa,EAAE,KAAa,EAAW,EAAE,CACvF,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;AAEzE,MAAM,eAAe,GAAG,CAAC,QAAgB,EAAE,KAAa,EAAW,EAAE,CACnE,kBAAkB,CAAC,IAAI,CAAC,CAAC,OAAe,EAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAA;AAEnG,+FAA+F;AAC/F,EAAE;AACF,yGAAyG;AACzG,sEAAsE;AACtE,EAAE;AACF,yGAAyG;AACzG,wGAAwG;AACxG,qGAAqG;AACrG,uGAAuG;AACvG,kGAAkG;AAClG,MAAM,eAAe,GAAW,YAAY,CAAA;AAE5C,qFAAqF;AACrF,MAAM,gBAAgB,GAAW,4BAA4B,CAAA;AAE7D,MAAM,oBAAoB,GAAG,CAAC,KAAa,EAAW,EAAE,CACtD,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;AAE1F;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,OAAe,EAAsC,EAAE;IACzF,MAAM,KAAK,GAAsB,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACpD,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,QAAgB,EAAE,KAAa,EAAsC,EAAE,CAC3F,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;SACpC,MAAM,CACL,CAAC,KAAsB,EAAW,EAAE,CAClC,CAAC,CACC,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC;QACtC,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC9B,oBAAoB,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CACtD,CACJ;SACA,GAAG,CACF,CAAC,KAAsB,EAA2B,EAAE,CAAC,CAAC;QACpD,IAAI,EAAE,KAAK,GAAG,CAAC;QACf,MAAM,EAAE,KAAK,CAAC,KAAK,GAAG,CAAC;QACvB,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;KAChB,CAAC,CACH,CACJ,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Escape every character a regular expression would read as syntax.
3
+ * @param text the literal to match.
4
+ * @returns the text, safe to embed in a pattern.
5
+ */
6
+ export declare const escapeForRegex: (text: string) => string;
7
+ //# sourceMappingURL=text-escapes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-escapes.d.ts","sourceRoot":"","sources":["../src/text-escapes.ts"],"names":[],"mappings":"AAQA;;;;GAIG;AACH,eAAO,MAAM,cAAc,GAAI,MAAM,MAAM,KAAG,MACU,CAAA"}
@@ -0,0 +1,14 @@
1
+ // One escaper, because two rules build a pattern out of text a project supplies.
2
+ //
3
+ // `secret-policy.ts` renders a declared exception path into a scanner's allowlist, and
4
+ // `payload-source.ts` searches for a literal call expression such as `payload.find(`. Both had their
5
+ // own copy of this function, character for character, in modules that never import each other - which
6
+ // is the shape of duplication that survives a review precisely because neither copy looks wrong.
7
+ const REGEX_METACHARACTERS = /[.*+?^${}()|[\]\\]/g;
8
+ /**
9
+ * Escape every character a regular expression would read as syntax.
10
+ * @param text the literal to match.
11
+ * @returns the text, safe to embed in a pattern.
12
+ */
13
+ export const escapeForRegex = (text) => text.replaceAll(REGEX_METACHARACTERS, String.raw `\$&`);
14
+ //# sourceMappingURL=text-escapes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-escapes.js","sourceRoot":"","sources":["../src/text-escapes.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,EAAE;AACF,uFAAuF;AACvF,qGAAqG;AACrG,sGAAsG;AACtG,iGAAiG;AACjG,MAAM,oBAAoB,GAAW,qBAAqB,CAAA;AAE1D;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,IAAY,EAAU,EAAE,CACrD,IAAI,CAAC,UAAU,CAAC,oBAAoB,EAAE,MAAM,CAAC,GAAG,CAAA,KAAK,CAAC,CAAA"}