llmnav 0.9.1 → 0.9.2
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/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/src/incremental.js +1 -1
- package/src/parser.js +69 -1
- package/src/spec.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,13 @@ The npm package follows Semantic Versioning. The `llmnav/N` source protocol is v
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.9.2] — 2026-09-09
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
* Recognize JavaScript/TypeScript regular-expression literals while masking source strings, so quotes and comment-like text inside regexes do not hide later cards or create false cards. Preserve division expressions and ordinary JSX closing tags.
|
|
14
|
+
* Invalidate older parsed-file caches so unchanged consumer files receive the corrected parsing behavior.
|
|
15
|
+
|
|
9
16
|
## [0.9.1] — 2026-09-07
|
|
10
17
|
|
|
11
18
|
### Fixed
|
package/package.json
CHANGED
package/src/incremental.js
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
} from "./util.js";
|
|
28
28
|
|
|
29
29
|
export const FILE_STATE_SCHEMA_VERSION = 1;
|
|
30
|
-
export const SOURCE_INDEXER_VERSION =
|
|
30
|
+
export const SOURCE_INDEXER_VERSION = 7;
|
|
31
31
|
const STAT_HINTS_SCHEMA_VERSION = 1;
|
|
32
32
|
|
|
33
33
|
export async function scanProjectIncremental(root, options = {}) {
|
package/src/parser.js
CHANGED
|
@@ -39,6 +39,11 @@ const BLOCK_PATTERNS = [
|
|
|
39
39
|
];
|
|
40
40
|
const MAX_SOURCE_BYTES = 16 * 1024 * 1024;
|
|
41
41
|
const MAX_BLOCKS_PER_FILE = 10_000;
|
|
42
|
+
const JAVASCRIPT_EXTENSIONS = new Set([".js", ".mjs", ".cjs", ".jsx", ".ts", ".tsx"]);
|
|
43
|
+
const REGEX_PREFIX_KEYWORDS = new Set([
|
|
44
|
+
"await", "case", "delete", "do", "else", "in", "instanceof", "new", "return", "throw", "typeof", "void", "yield",
|
|
45
|
+
]);
|
|
46
|
+
const CONTROL_PAREN_KEYWORDS = new Set(["if", "while", "for", "with", "switch", "catch"]);
|
|
42
47
|
|
|
43
48
|
export function parseLlmnavBlocks(source, filePath = "<memory>") {
|
|
44
49
|
if (Buffer.byteLength(source) > MAX_SOURCE_BYTES) {
|
|
@@ -217,6 +222,12 @@ function buildLiteralMask(source, filePath) {
|
|
|
217
222
|
const hashComments = [".py", ".rb", ".sh", ".bash", ".zsh"].includes(extension);
|
|
218
223
|
const dashComments = extension === ".sql";
|
|
219
224
|
const tripleQuotes = extension === ".py";
|
|
225
|
+
const javascript = JAVASCRIPT_EXTENSIONS.has(extension);
|
|
226
|
+
let regexAllowed = true;
|
|
227
|
+
let regexCharacterClass = false;
|
|
228
|
+
let previousToken = "";
|
|
229
|
+
const controlParentheses = [];
|
|
230
|
+
const expressionBraces = [];
|
|
220
231
|
let state = "normal";
|
|
221
232
|
let escaped = false;
|
|
222
233
|
const mask = new Uint8Array(source.length);
|
|
@@ -260,6 +271,21 @@ function buildLiteralMask(source, filePath) {
|
|
|
260
271
|
}
|
|
261
272
|
continue;
|
|
262
273
|
}
|
|
274
|
+
if (state === "regex") {
|
|
275
|
+
if (character === "\n" || character === "\r") {
|
|
276
|
+
state = "normal";
|
|
277
|
+
escaped = false;
|
|
278
|
+
} else if (escaped) escaped = false;
|
|
279
|
+
else if (character === "\\") escaped = true;
|
|
280
|
+
else if (character === "[") regexCharacterClass = true;
|
|
281
|
+
else if (character === "]") regexCharacterClass = false;
|
|
282
|
+
else if (character === "/" && !regexCharacterClass) {
|
|
283
|
+
state = "normal";
|
|
284
|
+
regexAllowed = false;
|
|
285
|
+
previousToken = "value";
|
|
286
|
+
}
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
263
289
|
if (state === "single" || state === "double" || state === "backtick") {
|
|
264
290
|
if (escaped) {
|
|
265
291
|
escaped = false;
|
|
@@ -270,7 +296,11 @@ function buildLiteralMask(source, filePath) {
|
|
|
270
296
|
continue;
|
|
271
297
|
}
|
|
272
298
|
const terminator = state === "single" ? "'" : state === "double" ? '"' : "`";
|
|
273
|
-
if (character === terminator)
|
|
299
|
+
if (character === terminator) {
|
|
300
|
+
state = "normal";
|
|
301
|
+
regexAllowed = false;
|
|
302
|
+
previousToken = "value";
|
|
303
|
+
}
|
|
274
304
|
continue;
|
|
275
305
|
}
|
|
276
306
|
|
|
@@ -283,6 +313,10 @@ function buildLiteralMask(source, filePath) {
|
|
|
283
313
|
} else if (character === "/" && next === "/") {
|
|
284
314
|
state = "line-comment";
|
|
285
315
|
index += 1;
|
|
316
|
+
} else if (javascript && character === "/" && regexAllowed) {
|
|
317
|
+
state = "regex";
|
|
318
|
+
regexCharacterClass = false;
|
|
319
|
+
escaped = false;
|
|
286
320
|
} else if (hashComments && character === "#") {
|
|
287
321
|
state = "line-comment";
|
|
288
322
|
} else if (dashComments && character === "-" && next === "-") {
|
|
@@ -303,6 +337,40 @@ function buildLiteralMask(source, filePath) {
|
|
|
303
337
|
} else if (character === "`") {
|
|
304
338
|
state = "backtick";
|
|
305
339
|
escaped = false;
|
|
340
|
+
} else if (javascript && !/\s/u.test(character)) {
|
|
341
|
+
// Track lexical expression position without rescanning prefixes. Comments do
|
|
342
|
+
// not change it; a control-condition ')' permits a following regex statement.
|
|
343
|
+
if (/[A-Za-z_$]/u.test(character)) {
|
|
344
|
+
const start = index;
|
|
345
|
+
const memberName = previousToken === ".";
|
|
346
|
+
while (index + 1 < source.length && /[\w$]/u.test(source[index + 1])) index += 1;
|
|
347
|
+
previousToken = source.slice(start, index + 1);
|
|
348
|
+
regexAllowed = !memberName && REGEX_PREFIX_KEYWORDS.has(previousToken);
|
|
349
|
+
} else if (character === "(") {
|
|
350
|
+
controlParentheses.push(CONTROL_PAREN_KEYWORDS.has(previousToken));
|
|
351
|
+
regexAllowed = true;
|
|
352
|
+
previousToken = character;
|
|
353
|
+
} else if (character === ")") {
|
|
354
|
+
regexAllowed = controlParentheses.pop() === true;
|
|
355
|
+
previousToken = character;
|
|
356
|
+
} else if (character === "{") {
|
|
357
|
+
expressionBraces.push(regexAllowed && !["", ";", "else", "do", "try", "finally", ")"].includes(previousToken));
|
|
358
|
+
regexAllowed = true;
|
|
359
|
+
previousToken = character;
|
|
360
|
+
} else if (character === "}") {
|
|
361
|
+
regexAllowed = expressionBraces.pop() !== true;
|
|
362
|
+
previousToken = character;
|
|
363
|
+
} else if ((character === "+" || character === "-") && next === character) {
|
|
364
|
+
previousToken = character + next;
|
|
365
|
+
index += 1;
|
|
366
|
+
} else if (character === "=" && next === ">") {
|
|
367
|
+
regexAllowed = true;
|
|
368
|
+
previousToken = "=>";
|
|
369
|
+
index += 1;
|
|
370
|
+
} else {
|
|
371
|
+
regexAllowed = "=(:,!&|?;[+-*%~^>/".includes(character);
|
|
372
|
+
previousToken = character;
|
|
373
|
+
}
|
|
306
374
|
}
|
|
307
375
|
}
|
|
308
376
|
|
package/src/spec.js
CHANGED
|
@@ -10,7 +10,7 @@ rel=workflow>llmnav.rules.validate
|
|
|
10
10
|
stability=contract
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
export const PACKAGE_VERSION = "0.9.
|
|
13
|
+
export const PACKAGE_VERSION = "0.9.2";
|
|
14
14
|
export const SPEC_VERSION = "1";
|
|
15
15
|
|
|
16
16
|
export const SCOPES = Object.freeze(["file", "module", "symbol"]);
|