ctxman 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/.ctxman/llm-profiles.json +1067 -39
  2. package/.ctxman/profiles/backend-team.json +13 -0
  3. package/.ctxman/profiles/default.json +12 -0
  4. package/.ctxman/profiles/devops-team.json +21 -0
  5. package/.ctxman/profiles/frontend-team.json +20 -0
  6. package/.ctxman/templates/bug-fix.json +17 -0
  7. package/.ctxman/templates/code-review.json +9 -0
  8. package/.ctxman/templates/documentation.json +9 -0
  9. package/.ctxman/templates/feature.json +9 -0
  10. package/.ctxman/templates/full-context.json +9 -0
  11. package/.ctxman/templates/refactoring.json +18 -0
  12. package/.ctxman/templates/testing.json +9 -0
  13. package/.ctxman/wizard-profiles/security-audit/profile.json +7 -1
  14. package/LICENSE +21 -0
  15. package/README.md +380 -136
  16. package/bin/cli.js +1880 -529
  17. package/bin/cm-ask.js +18 -13
  18. package/bin/cm-audit.js +124 -0
  19. package/bin/cm-count.js +151 -0
  20. package/bin/cm-gitingest.js +128 -115
  21. package/bin/cm-map.js +242 -0
  22. package/bin/cm-mcp.js +293 -0
  23. package/bin/cm-pack.js +405 -0
  24. package/bin/cm-policy.js +170 -0
  25. package/bin/cm-pr.js +248 -0
  26. package/bin/cm-search.js +178 -0
  27. package/bin/cm-update.js +128 -128
  28. package/bin/cm-why.js +197 -0
  29. package/bin/mcp-server.js +8 -24
  30. package/ctxman.js +237 -183
  31. package/examples/README.md +19 -4
  32. package/examples/github-actions/ctxman-check.yml +49 -0
  33. package/examples/wizard-profiles/security-audit/profile.json +7 -1
  34. package/index.d.ts +1422 -0
  35. package/index.js +55 -31
  36. package/lib/agents/pr-failure-agent.js +48 -45
  37. package/lib/analyzers/ai-suggester.js +451 -0
  38. package/lib/analyzers/dependency-scanner.js +505 -0
  39. package/lib/analyzers/duplicate-detector.js +264 -0
  40. package/lib/analyzers/go-method-analyzer.js +151 -110
  41. package/lib/analyzers/import-tracker.js +431 -0
  42. package/lib/analyzers/method-analyzer.js +1055 -434
  43. package/lib/analyzers/performance-dashboard.js +683 -0
  44. package/lib/analyzers/token-calculator.js +895 -504
  45. package/lib/analyzers/unused-exports-detector.js +336 -0
  46. package/lib/api/mcp/install.js +424 -0
  47. package/lib/api/mcp/prompts.js +281 -281
  48. package/lib/api/mcp/resources.js +300 -241
  49. package/lib/api/mcp/sandbox.js +89 -0
  50. package/lib/api/mcp/server.js +675 -312
  51. package/lib/api/mcp/start.js +125 -0
  52. package/lib/api/mcp/transports/HttpSseTransport.js +60 -52
  53. package/lib/api/rest/server.js +105 -28
  54. package/lib/cache/CacheManager.js +9 -17
  55. package/lib/core/Analyzer.js +16 -10
  56. package/lib/core/ContextBuilder.js +19 -28
  57. package/lib/core/Reporter.js +15 -14
  58. package/lib/core/Scanner.js +81 -12
  59. package/lib/count/index.js +100 -0
  60. package/lib/formatters/format-registry.js +394 -372
  61. package/lib/formatters/gitingest-formatter.js +656 -621
  62. package/lib/formatters/toon-formatter-v1.3.js +637 -632
  63. package/lib/formatters/toon-formatter.js +239 -240
  64. package/lib/integrations/git/BlameTracker.js +4 -4
  65. package/lib/integrations/git/DiffAnalyzer.js +38 -31
  66. package/lib/integrations/git/GitClient.js +71 -53
  67. package/lib/lsp/server.js +311 -0
  68. package/lib/map/graph.js +299 -0
  69. package/lib/map/imports.js +233 -0
  70. package/lib/map/index.js +33 -0
  71. package/lib/map/rank.js +253 -0
  72. package/lib/map/render.js +346 -0
  73. package/lib/map/resolver.js +574 -0
  74. package/lib/pack/budget.js +111 -0
  75. package/lib/pack/code-lexer.js +360 -0
  76. package/lib/pack/compress.js +414 -0
  77. package/lib/pack/index.js +354 -0
  78. package/lib/pack/renderers/common.js +134 -0
  79. package/lib/pack/renderers/index.js +24 -0
  80. package/lib/pack/renderers/json.js +47 -0
  81. package/lib/pack/renderers/markdown.js +54 -0
  82. package/lib/pack/renderers/plain.js +38 -0
  83. package/lib/pack/renderers/xml.js +85 -0
  84. package/lib/pack/select.js +177 -0
  85. package/lib/pack/split.js +182 -0
  86. package/lib/pack/tree.js +35 -0
  87. package/lib/parsers/gitignore-parser.js +113 -107
  88. package/lib/parsers/method-filter-parser.js +50 -33
  89. package/lib/plugins/ExporterPlugin.js +3 -3
  90. package/lib/plugins/LanguagePlugin.js +3 -3
  91. package/lib/plugins/PluginManager.js +7 -13
  92. package/lib/policy/audit.js +294 -0
  93. package/lib/policy/enforce.js +125 -0
  94. package/lib/policy/ignore-files.js +169 -0
  95. package/lib/policy/index.js +52 -0
  96. package/lib/policy/policy.js +489 -0
  97. package/lib/pr/diff-parser.js +267 -0
  98. package/lib/pr/method-mapper.js +148 -0
  99. package/lib/pr/pr-context.js +829 -0
  100. package/lib/pr/pr-git.js +149 -0
  101. package/lib/pr/pr-renderer.js +644 -0
  102. package/lib/pr/related-tests.js +296 -0
  103. package/lib/rag/DataSourcePlugin.js +5 -5
  104. package/lib/rag/EmbeddingProvider.js +19 -8
  105. package/lib/rag/Indexer.js +14 -5
  106. package/lib/rag/VectorStoreFactory.js +3 -3
  107. package/lib/rag/sources/FileDataSource.js +6 -6
  108. package/lib/rag/stores/BaseVectorStore.js +5 -5
  109. package/lib/rag/stores/ChromaDBStore.js +10 -10
  110. package/lib/rag/stores/LanceDBStore.js +29 -19
  111. package/lib/rag/stores/PineconeStore.js +11 -9
  112. package/lib/search/bm25.js +433 -0
  113. package/lib/search/code-units.js +201 -0
  114. package/lib/search/symbols.js +189 -0
  115. package/lib/ui/dashboard.js +120 -89
  116. package/lib/ui/index.js +1 -6
  117. package/lib/ui/init-wizard-ui.js +550 -0
  118. package/lib/ui/progress-bar.js +94 -88
  119. package/lib/ui/select-input.js +40 -32
  120. package/lib/ui/template-selector.js +99 -0
  121. package/lib/ui/wizard.js +374 -329
  122. package/lib/utils/claude-token-counter.js +151 -0
  123. package/lib/utils/cli-output.js +17 -0
  124. package/lib/utils/clipboard-utils.js +49 -50
  125. package/lib/utils/config-utils.js +81 -66
  126. package/lib/utils/context-versioning.js +532 -0
  127. package/lib/utils/error-handler.js +112 -112
  128. package/lib/utils/file-utils.js +153 -69
  129. package/lib/utils/format-converter.js +356 -283
  130. package/lib/utils/git-utils.js +366 -294
  131. package/lib/utils/llm-cost-estimator.js +281 -0
  132. package/lib/utils/llm-detector.js +242 -78
  133. package/lib/utils/logger.js +243 -237
  134. package/lib/utils/messages.js +355 -0
  135. package/lib/utils/multi-repo-manager.js +364 -0
  136. package/lib/utils/profile-manager.js +380 -0
  137. package/lib/utils/safe-read.js +61 -0
  138. package/lib/utils/secret-redactor.js +61 -0
  139. package/lib/utils/snapshot-manager.js +679 -0
  140. package/lib/utils/template-manager.js +378 -0
  141. package/lib/utils/token-utils.js +201 -170
  142. package/lib/utils/tokenizer-adapter.js +421 -377
  143. package/lib/utils/updater.js +539 -506
  144. package/lib/watch/FileWatcher.js +12 -8
  145. package/lib/watch/IncrementalAnalyzer.js +7 -7
  146. package/lib/why/coupling.js +81 -0
  147. package/lib/why/errors.js +23 -0
  148. package/lib/why/git.js +169 -0
  149. package/lib/why/history.js +108 -0
  150. package/lib/why/index.js +720 -0
  151. package/lib/why/ownership.js +79 -0
  152. package/lib/why/paths.js +207 -0
  153. package/lib/why/references.js +259 -0
  154. package/lib/why/render.js +722 -0
  155. package/lib/why/search.js +358 -0
  156. package/lib/why/target.js +389 -0
  157. package/lib/wizards/config-generator.js +255 -0
  158. package/lib/wizards/project-detector.js +571 -0
  159. package/package.json +44 -10
  160. package/.ctxman/logs/ctxman-2026-07-02.log +0 -156
  161. package/.ctxman/self-knowledge/documents.lance/_transactions/0-b3dd1558-f95b-44b9-a154-18e8c330ae7a.txn +0 -0
  162. package/.ctxman/self-knowledge/documents.lance/_transactions/1-44bff6d4-e552-4010-a769-e09ad822698d.txn +0 -0
  163. package/.ctxman/self-knowledge/documents.lance/_transactions/10-37260794-5947-4ecb-b4c8-d732f58f40f1.txn +0 -0
  164. package/.ctxman/self-knowledge/documents.lance/_transactions/11-0d52a883-5518-463c-9b6d-986c3af89dfd.txn +0 -0
  165. package/.ctxman/self-knowledge/documents.lance/_transactions/12-ae354646-52f2-49c9-90fb-2e6ed8932663.txn +0 -0
  166. package/.ctxman/self-knowledge/documents.lance/_transactions/13-5d7d5e57-27da-498b-b734-091b9d38da13.txn +0 -0
  167. package/.ctxman/self-knowledge/documents.lance/_transactions/14-558ce928-810b-4252-a364-fdd1ee6f8a25.txn +0 -0
  168. package/.ctxman/self-knowledge/documents.lance/_transactions/15-27e3a58f-8b64-43b8-a9f4-e3369446968b.txn +0 -0
  169. package/.ctxman/self-knowledge/documents.lance/_transactions/16-a110c390-e5c8-4464-b976-bc7f5195fa15.txn +0 -0
  170. package/.ctxman/self-knowledge/documents.lance/_transactions/17-97513225-53de-4216-b61a-6337bd0cc4ad.txn +0 -0
  171. package/.ctxman/self-knowledge/documents.lance/_transactions/18-5ba450f0-c527-459c-bf4f-6d79b5e4285f.txn +0 -0
  172. package/.ctxman/self-knowledge/documents.lance/_transactions/19-600d3a63-5225-49e9-b8b7-30e037b564ab.txn +0 -0
  173. package/.ctxman/self-knowledge/documents.lance/_transactions/2-48a61b4e-92cf-4342-8376-914c1b99a9f9.txn +0 -0
  174. package/.ctxman/self-knowledge/documents.lance/_transactions/20-121dd912-5410-410e-84ff-82c7a23604a0.txn +0 -0
  175. package/.ctxman/self-knowledge/documents.lance/_transactions/3-bc6844ab-7ea3-4bb3-9ee1-538ce5664a9c.txn +0 -0
  176. package/.ctxman/self-knowledge/documents.lance/_transactions/4-d5e1754f-dbb5-454e-a9be-2a7da8d7bcf9.txn +0 -0
  177. package/.ctxman/self-knowledge/documents.lance/_transactions/5-4fae93df-0568-4b6f-8acf-8bef4bba6047.txn +0 -0
  178. package/.ctxman/self-knowledge/documents.lance/_transactions/6-4b1b8b87-068e-49a3-ba2b-d8dae628b252.txn +0 -0
  179. package/.ctxman/self-knowledge/documents.lance/_transactions/7-63d040fb-d2fa-4c0f-bed0-53411918a7b0.txn +0 -0
  180. package/.ctxman/self-knowledge/documents.lance/_transactions/8-c5628cf6-264c-420f-aab8-bc96a9083591.txn +0 -0
  181. package/.ctxman/self-knowledge/documents.lance/_transactions/9-c94570aa-5e53-4fcb-971f-cecc1d1d5492.txn +0 -0
  182. package/.ctxman/self-knowledge/documents.lance/_versions/1.manifest +0 -0
  183. package/.ctxman/self-knowledge/documents.lance/_versions/10.manifest +0 -0
  184. package/.ctxman/self-knowledge/documents.lance/_versions/11.manifest +0 -0
  185. package/.ctxman/self-knowledge/documents.lance/_versions/12.manifest +0 -0
  186. package/.ctxman/self-knowledge/documents.lance/_versions/13.manifest +0 -0
  187. package/.ctxman/self-knowledge/documents.lance/_versions/14.manifest +0 -0
  188. package/.ctxman/self-knowledge/documents.lance/_versions/15.manifest +0 -0
  189. package/.ctxman/self-knowledge/documents.lance/_versions/16.manifest +0 -0
  190. package/.ctxman/self-knowledge/documents.lance/_versions/17.manifest +0 -0
  191. package/.ctxman/self-knowledge/documents.lance/_versions/18.manifest +0 -0
  192. package/.ctxman/self-knowledge/documents.lance/_versions/19.manifest +0 -0
  193. package/.ctxman/self-knowledge/documents.lance/_versions/2.manifest +0 -0
  194. package/.ctxman/self-knowledge/documents.lance/_versions/20.manifest +0 -0
  195. package/.ctxman/self-knowledge/documents.lance/_versions/21.manifest +0 -0
  196. package/.ctxman/self-knowledge/documents.lance/_versions/3.manifest +0 -0
  197. package/.ctxman/self-knowledge/documents.lance/_versions/4.manifest +0 -0
  198. package/.ctxman/self-knowledge/documents.lance/_versions/5.manifest +0 -0
  199. package/.ctxman/self-knowledge/documents.lance/_versions/6.manifest +0 -0
  200. package/.ctxman/self-knowledge/documents.lance/_versions/7.manifest +0 -0
  201. package/.ctxman/self-knowledge/documents.lance/_versions/8.manifest +0 -0
  202. package/.ctxman/self-knowledge/documents.lance/_versions/9.manifest +0 -0
  203. package/.ctxman/self-knowledge/documents.lance/data/000010010011011101000010de8621405d9d5cb06540dbf02c.lance +0 -0
  204. package/.ctxman/self-knowledge/documents.lance/data/0000101001011100011000003fb62a4aa8978974075c144f1c.lance +0 -0
  205. package/.ctxman/self-knowledge/documents.lance/data/000011010000100111001011c6cc824d628f3bb1f4e04cd5d0.lance +0 -0
  206. package/.ctxman/self-knowledge/documents.lance/data/001000001000011001011100d8f74349ada090b332cd1df669.lance +0 -0
  207. package/.ctxman/self-knowledge/documents.lance/data/01000001111010001000010045ab44467bb1e7e4d8ae06bd58.lance +0 -0
  208. package/.ctxman/self-knowledge/documents.lance/data/01000101101110111110011131ef904241a89e1ffb6ddaf8e3.lance +0 -0
  209. package/.ctxman/self-knowledge/documents.lance/data/010010110000010000011010fbfcc145c6a8a827b28057a07e.lance +0 -0
  210. package/.ctxman/self-knowledge/documents.lance/data/1000011000101010001110003fb4f045c3835982efa1405324.lance +0 -0
  211. package/.ctxman/self-knowledge/documents.lance/data/100111011101110111101011f4396b410fa06af3b028d204e6.lance +0 -0
  212. package/.ctxman/self-knowledge/documents.lance/data/100111100111101000001100596b174e00995d6cc2969cc8dc.lance +0 -0
  213. package/.ctxman/self-knowledge/documents.lance/data/1010001100011000110010102ea16b484d887c7c9725feffd6.lance +0 -0
  214. package/.ctxman/self-knowledge/documents.lance/data/101101100010101111110111c5230944d9b3af7301b6392dda.lance +0 -0
  215. package/.ctxman/self-knowledge/documents.lance/data/1011011100100111111110105892bf41b5a530fedd7cf516f1.lance +0 -0
  216. package/.ctxman/self-knowledge/documents.lance/data/10111110000010001011110164e8774742bade87b483c10f36.lance +0 -0
  217. package/.ctxman/self-knowledge/documents.lance/data/1100000100010110001110103ffaca40a2a42c85067ebaf9ae.lance +0 -0
  218. package/.ctxman/self-knowledge/documents.lance/data/110000010001110110101110664bde4c76a78dfc14b05e83b8.lance +0 -0
  219. package/.ctxman/self-knowledge/documents.lance/data/11001100111000010100110050645c4281b1f35b2022234f7f.lance +0 -0
  220. package/.ctxman/self-knowledge/documents.lance/data/1101111011001100110111017214924a368f5719eab2246619.lance +0 -0
  221. package/.ctxman/self-knowledge/documents.lance/data/1110000111001010001000002150c24dcab653d39d07032e63.lance +0 -0
  222. package/.ctxman/self-knowledge/documents.lance/data/1110100000000100000011014d20534967a18b5c87721659ff.lance +0 -0
  223. package/.ctxman/self-knowledge/documents.lance/data/11110101111010000101011135a0114ef2b35dc6f1297a3381.lance +0 -0
@@ -0,0 +1,360 @@
1
+ /**
2
+ * Code Lexer for `pack --compress`
3
+ *
4
+ * Marks every character of a source file as code, comment or string and pairs up the
5
+ * brackets that are code, so the compressor can find a function body's braces without being
6
+ * fooled by braces inside strings, comments, regular expressions or template text. It knows
7
+ * each language's comment and string syntax only; it is not a parser.
8
+ */
9
+
10
+ import path from 'path';
11
+
12
+ /** Character kinds stored in `kinds` */
13
+ export const CODE = 0;
14
+ export const COMMENT = 1;
15
+ export const STRING = 2;
16
+
17
+ const C_LIKE = { lineComments: ['//'], blockComments: true };
18
+
19
+ /**
20
+ * Comment and string syntax per language family. Options:
21
+ * quotes: single-line string delimiters, multilineQuotes: those strings may span lines,
22
+ * triple: multi-line delimiters ("""), charLiterals: `'x'` is a character literal and any
23
+ * other `'` is code (Rust lifetimes, Scala symbols), template: JavaScript template literals,
24
+ * regex: JavaScript regular expression literals.
25
+ */
26
+ const SYNTAX = {
27
+ js: { ...C_LIKE, quotes: '"\'', template: true, regex: true },
28
+ java: { ...C_LIKE, quotes: '"', triple: ['"""'], charLiterals: true },
29
+ csharp: { ...C_LIKE, quotes: '"', triple: ['"""'], verbatim: true, charLiterals: true },
30
+ go: { ...C_LIKE, quotes: '"', rawBacktick: true, charLiterals: true },
31
+ rust: {
32
+ ...C_LIKE,
33
+ nestedComments: true,
34
+ quotes: '"',
35
+ multilineQuotes: true,
36
+ charLiterals: true,
37
+ rustRaw: true,
38
+ },
39
+ php: {
40
+ lineComments: ['//', '#'],
41
+ blockComments: true,
42
+ quotes: '"\'',
43
+ multilineQuotes: true,
44
+ heredoc: true,
45
+ },
46
+ kotlin: { ...C_LIKE, nestedComments: true, quotes: '"', triple: ['"""'], charLiterals: true },
47
+ swift: { ...C_LIKE, nestedComments: true, quotes: '"', triple: ['"""'] },
48
+ c: { ...C_LIKE, quotes: '"', charLiterals: true },
49
+ scala: { ...C_LIKE, nestedComments: true, quotes: '"', triple: ['"""'], charLiterals: true },
50
+ python: { lineComments: ['#'], quotes: '"\'', triple: ['"""', "'''"] },
51
+ };
52
+
53
+ /** Extensions whose method analyzer the compressor can use, by language family */
54
+ const FAMILY_BY_EXTENSION = {
55
+ '.js': 'js',
56
+ '.jsx': 'js',
57
+ '.mjs': 'js',
58
+ '.cjs': 'js',
59
+ '.ts': 'js',
60
+ '.tsx': 'js',
61
+ '.mts': 'js',
62
+ '.cts': 'js',
63
+ '.java': 'java',
64
+ '.cs': 'csharp',
65
+ '.go': 'go',
66
+ '.rs': 'rust',
67
+ '.php': 'php',
68
+ '.kt': 'kotlin',
69
+ '.kts': 'kotlin',
70
+ '.swift': 'swift',
71
+ '.c': 'c',
72
+ '.cc': 'c',
73
+ '.cpp': 'c',
74
+ '.h': 'c',
75
+ '.hpp': 'c',
76
+ '.scala': 'scala',
77
+ '.py': 'python',
78
+ '.rb': 'ruby',
79
+ };
80
+
81
+ const OPENERS = { '(': ')', '[': ']', '{': '}' };
82
+ const CLOSERS = new Set([')', ']', '}']);
83
+
84
+ // After one of these, a `/` starts a regular expression rather than a division
85
+ const REGEX_PRECEDERS = new Set('(,=:[!&|?{};+-*%<>~^}'.split(''));
86
+ const REGEX_KEYWORDS = new Set([
87
+ 'return',
88
+ 'typeof',
89
+ 'instanceof',
90
+ 'case',
91
+ 'do',
92
+ 'else',
93
+ 'in',
94
+ 'of',
95
+ 'new',
96
+ 'delete',
97
+ 'void',
98
+ 'throw',
99
+ 'yield',
100
+ 'await',
101
+ ]);
102
+
103
+ const CHAR_LITERAL = /'(?:\\.[^'\n]{0,9}|[^'\\\n])'/y;
104
+ const RUST_RAW_STRING = /b?r(#*)"/y;
105
+ const PHP_HEREDOC = /<<<[ \t]*(['"]?)([A-Za-z_]\w*)\1\r?\n/y;
106
+ const WORD_CHAR = /[\w$]/;
107
+
108
+ /**
109
+ * Language family of a file for the compressor, or null when it has no method analyzer
110
+ * @param {string} filePath
111
+ * @returns {string|null} 'js', 'python', 'ruby', ...
112
+ */
113
+ export function familyOf(filePath) {
114
+ return FAMILY_BY_EXTENSION[path.extname(filePath).toLowerCase()] ?? null;
115
+ }
116
+
117
+ /**
118
+ * Index of the first character of every line
119
+ * @param {string} src
120
+ * @returns {number[]}
121
+ */
122
+ export function lineStartsOf(src) {
123
+ const starts = [0];
124
+ for (let i = src.indexOf('\n'); i !== -1; i = src.indexOf('\n', i + 1)) starts.push(i + 1);
125
+ return starts;
126
+ }
127
+
128
+ /**
129
+ * Zero-based line of a character index
130
+ * @param {number[]} lineStarts - From lineStartsOf()
131
+ * @param {number} index
132
+ * @returns {number}
133
+ */
134
+ export function lineAt(lineStarts, index) {
135
+ let lo = 0;
136
+ let hi = lineStarts.length - 1;
137
+ while (lo < hi) {
138
+ const mid = (lo + hi + 1) >> 1;
139
+ if (lineStarts[mid] <= index) lo = mid;
140
+ else hi = mid - 1;
141
+ }
142
+ return lo;
143
+ }
144
+
145
+ /**
146
+ * Lex a source file
147
+ * @param {string} src - File content
148
+ * @param {string} family - From familyOf() (not 'ruby')
149
+ * @returns {{kinds: Uint8Array, match: Int32Array, balanced: boolean, lineStarts: number[]}}
150
+ * kinds: CODE/COMMENT/STRING per character; match: index of the partner of each code
151
+ * bracket (-1 elsewhere); balanced: false when a bracket has no partner, in which case
152
+ * the pairs cannot be trusted
153
+ */
154
+ export function lexCode(src, family) {
155
+ const syntax = SYNTAX[family];
156
+ if (!syntax) throw new Error(`No lexer for ${family}`);
157
+
158
+ const n = src.length;
159
+ const kinds = new Uint8Array(n);
160
+ const match = new Int32Array(n).fill(-1);
161
+ const stack = [];
162
+ const templateDepths = []; // JS: brace depth inside each open `${ ... }`
163
+ let balanced = true;
164
+ let prev = -1; // last significant code character, for regex detection
165
+
166
+ const mark = (from, to, kind) => kinds.fill(kind, from, Math.min(to, n));
167
+
168
+ /** End of a quoted string starting at `start` (the opening delimiter) */
169
+ const quotedEnd = (start, quote, multiline) => {
170
+ for (let j = start + 1; j < n; j++) {
171
+ const c = src[j];
172
+ if (c === '\\') j++;
173
+ else if (c === quote) return j + 1;
174
+ else if (c === '\n' && !multiline) return j; // unterminated: stop at the line end
175
+ }
176
+ return n;
177
+ };
178
+
179
+ /** End of template text starting at `start`, and whether it stopped at a `${` */
180
+ const templateEnd = (start) => {
181
+ for (let j = start; j < n; j++) {
182
+ const c = src[j];
183
+ if (c === '\\') j++;
184
+ else if (c === '`') return { end: j + 1, expression: false };
185
+ else if (c === '$' && src[j + 1] === '{') return { end: j + 1, expression: true };
186
+ }
187
+ return { end: n, expression: false };
188
+ };
189
+
190
+ const regexAllowed = () => {
191
+ if (prev < 0) return true;
192
+ const p = src[prev];
193
+ if (REGEX_PRECEDERS.has(p)) return true;
194
+ if (!WORD_CHAR.test(p)) return false;
195
+ let s = prev;
196
+ while (s > 0 && WORD_CHAR.test(src[s - 1])) s--;
197
+ return REGEX_KEYWORDS.has(src.slice(s, prev + 1));
198
+ };
199
+
200
+ /** End of a regex literal starting at `start`, or -1 when it does not close on its line */
201
+ const regexEnd = (start) => {
202
+ let inClass = false;
203
+ for (let j = start + 1; j < n; j++) {
204
+ const c = src[j];
205
+ if (c === '\n') return -1;
206
+ if (c === '\\') j++;
207
+ else if (c === '[') inClass = true;
208
+ else if (c === ']') inClass = false;
209
+ else if (c === '/' && !inClass) {
210
+ let end = j + 1;
211
+ while (end < n && /[a-z]/i.test(src[end])) end++;
212
+ return end;
213
+ }
214
+ }
215
+ return -1;
216
+ };
217
+
218
+ const openBracket = (i) => stack.push(i);
219
+ const closeBracket = (i) => {
220
+ const open = stack.pop();
221
+ if (open === undefined || OPENERS[src[open]] !== src[i]) {
222
+ balanced = false;
223
+ if (open !== undefined) stack.push(open);
224
+ return;
225
+ }
226
+ match[open] = i;
227
+ match[i] = open;
228
+ };
229
+
230
+ /** Mark template text from `start` and resume code after it (or inside its `${`) */
231
+ const continueTemplate = (start) => {
232
+ const { end, expression } = templateEnd(start);
233
+ mark(start, end, STRING);
234
+ if (!expression) {
235
+ prev = end - 1;
236
+ return end;
237
+ }
238
+ // `end` is the `{` of `${`: a code bracket closed by the `}` that ends the expression
239
+ openBracket(end);
240
+ templateDepths.push(0);
241
+ prev = end;
242
+ return end + 1;
243
+ };
244
+
245
+ let i = 0;
246
+ while (i < n) {
247
+ const ch = src[i];
248
+ const next = src[i + 1];
249
+
250
+ // Comments
251
+ const lineComment = syntax.lineComments.find((token) => src.startsWith(token, i));
252
+ if (lineComment && !(family === 'php' && ch === '#' && next === '[')) {
253
+ const end = src.indexOf('\n', i);
254
+ const stop = end === -1 ? n : end;
255
+ mark(i, stop, COMMENT);
256
+ i = stop;
257
+ continue;
258
+ }
259
+ if (syntax.blockComments && ch === '/' && next === '*') {
260
+ let depth = 1;
261
+ let j = i + 2;
262
+ while (j < n && depth > 0) {
263
+ if (src[j] === '*' && src[j + 1] === '/') {
264
+ depth--;
265
+ j += 2;
266
+ } else if (syntax.nestedComments && src[j] === '/' && src[j + 1] === '*') {
267
+ depth++;
268
+ j += 2;
269
+ } else {
270
+ j++;
271
+ }
272
+ }
273
+ mark(i, j, COMMENT);
274
+ i = j;
275
+ continue;
276
+ }
277
+
278
+ // Strings
279
+ let end = -1;
280
+ const triple = syntax.triple?.find((token) => src.startsWith(token, i));
281
+ if (triple) {
282
+ const close = src.indexOf(triple, i + 3);
283
+ end = close === -1 ? n : close + 3;
284
+ } else if (
285
+ syntax.verbatim &&
286
+ ch === '"' &&
287
+ (src[i - 1] === '@' || (src[i - 1] === '$' && src[i - 2] === '@'))
288
+ ) {
289
+ // C# verbatim string: spans lines, `""` is an escaped quote
290
+ end = n;
291
+ for (let j = i + 1; j < n; j++) {
292
+ if (src[j] !== '"') continue;
293
+ if (src[j + 1] === '"') j++;
294
+ else {
295
+ end = j + 1;
296
+ break;
297
+ }
298
+ }
299
+ } else if (syntax.quotes.includes(ch)) {
300
+ end = quotedEnd(i, ch, syntax.multilineQuotes);
301
+ } else if (syntax.charLiterals && ch === "'") {
302
+ CHAR_LITERAL.lastIndex = i;
303
+ if (CHAR_LITERAL.test(src)) end = CHAR_LITERAL.lastIndex;
304
+ } else if (syntax.rawBacktick && ch === '`') {
305
+ const close = src.indexOf('`', i + 1);
306
+ end = close === -1 ? n : close + 1;
307
+ } else if (syntax.rustRaw && (ch === 'r' || ch === 'b') && !WORD_CHAR.test(src[i - 1] ?? ' ')) {
308
+ RUST_RAW_STRING.lastIndex = i;
309
+ const raw = RUST_RAW_STRING.exec(src);
310
+ if (raw) {
311
+ const close = src.indexOf(`"${raw[1]}`, RUST_RAW_STRING.lastIndex);
312
+ end = close === -1 ? n : close + 1 + raw[1].length;
313
+ }
314
+ } else if (syntax.heredoc && ch === '<' && src.startsWith('<<<', i)) {
315
+ PHP_HEREDOC.lastIndex = i;
316
+ const heredoc = PHP_HEREDOC.exec(src);
317
+ if (heredoc) {
318
+ const closing = new RegExp(`^[ \\t]*${heredoc[2]}\\b`, 'm');
319
+ const rest = src.slice(PHP_HEREDOC.lastIndex);
320
+ const found = closing.exec(rest);
321
+ end = found ? PHP_HEREDOC.lastIndex + found.index + found[0].length : n;
322
+ }
323
+ } else if (syntax.template && ch === '`') {
324
+ mark(i, i + 1, STRING);
325
+ i = continueTemplate(i + 1);
326
+ continue;
327
+ } else if (syntax.regex && ch === '/' && regexAllowed()) {
328
+ end = regexEnd(i);
329
+ }
330
+ if (end > i) {
331
+ mark(i, end, STRING);
332
+ prev = end - 1;
333
+ i = end;
334
+ continue;
335
+ }
336
+
337
+ // Code
338
+ if (ch in OPENERS) {
339
+ openBracket(i);
340
+ if (ch === '{' && templateDepths.length) templateDepths[templateDepths.length - 1]++;
341
+ } else if (CLOSERS.has(ch)) {
342
+ if (ch === '}' && templateDepths.length && templateDepths[templateDepths.length - 1] === 0) {
343
+ // End of a `${ ... }` expression: back into the template text
344
+ templateDepths.pop();
345
+ closeBracket(i);
346
+ i = continueTemplate(i + 1);
347
+ continue;
348
+ }
349
+ if (ch === '}' && templateDepths.length) templateDepths[templateDepths.length - 1]--;
350
+ closeBracket(i);
351
+ }
352
+ if (ch !== ' ' && ch !== '\t' && ch !== '\n' && ch !== '\r') prev = i;
353
+ i++;
354
+ }
355
+
356
+ if (stack.length || templateDepths.length) balanced = false;
357
+ return { kinds, match, balanced, lineStarts: lineStartsOf(src) };
358
+ }
359
+
360
+ export default { lexCode, familyOf, lineStartsOf, lineAt, CODE, COMMENT, STRING };