i18next-cli 1.59.1 → 1.60.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -280,6 +280,8 @@ Analyzes your source code for internationalization issues like hardcoded strings
280
280
  npx i18next-cli lint
281
281
  ```
282
282
 
283
+ To suppress warnings for code you intentionally aren't translating yet, use the [`i18next-instrument-ignore` directive](#suppressing-detection-with-i18next-instrument-ignore) — the same comment recognized by the `instrument` command.
284
+
283
285
  ### `instrument`
284
286
 
285
287
  Scans your source code for hardcoded user-facing strings and instruments them with i18next translation calls. This is useful for adding i18next instrumentation to an existing codebase that wasn't built with internationalization in mind. You can see this in action in [this video](https://youtu.be/aWZnZXwGg34) or in [this blog post](https://www.locize.com/blog/i18next-cli-instrument?utm_source=i18next_cli_readme&utm_medium=github&utm_campaign=readme).
@@ -437,6 +439,33 @@ The intended usage pattern is:
437
439
  5. Manually fix any false positives or false negatives
438
440
  6. Run `extract` to finalize translation files
439
441
 
442
+ #### Suppressing detection with `i18next-instrument-ignore`
443
+
444
+ Both the `lint` and `instrument` commands honor an ignore comment so you can skip placeholder or intentionally-untranslated content. It works as a line or block comment, including the JSX `{/* ... */}` form, and comes in two variants:
445
+
446
+ | Directive | Scope |
447
+ | --- | --- |
448
+ | `i18next-instrument-ignore` | The **entire JSX element** that begins on the next line — its opening tag, all nested children, and its closing tag. Falls back to a single line when the next line isn't a JSX element (e.g. a plain `t()` call). |
449
+ | `i18next-instrument-ignore-next-line` | Only the **single line** immediately after the directive. |
450
+
451
+ ```jsx
452
+ // Suppress a whole element (including multi-line opening tags and nested children)
453
+ {/* i18next-instrument-ignore */}
454
+ <div
455
+ css={css`text-align: center;`}>
456
+ Hi, I'm Bob 👋
457
+ <p>This nested text is ignored too</p>
458
+ </div>
459
+
460
+ // Suppress just one line
461
+ {/* i18next-instrument-ignore-next-line */}
462
+ <p>Only this line is ignored</p>
463
+
464
+ // Also works for t() interpolation warnings in the linter
465
+ // i18next-instrument-ignore
466
+ const msg = t('Hello {{name}}!', { wrong: 'world' })
467
+ ```
468
+
440
469
  ### `migrate-config`
441
470
  Automatically migrates a legacy `i18next-parser.config.js` file to the new `i18next.config.ts` format.
442
471
 
package/dist/cjs/cli.js CHANGED
@@ -32,7 +32,7 @@ const program = new commander.Command();
32
32
  program
33
33
  .name('i18next-cli')
34
34
  .description('A unified, high-performance i18next CLI.')
35
- .version('1.59.1'); // This string is replaced with the actual version at build time by rollup
35
+ .version('1.60.0'); // This string is replaced with the actual version at build time by rollup
36
36
  // new: global config override option
37
37
  program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
38
38
  program
@@ -103,6 +103,172 @@ function lineColumnFromOffset(code, offset) {
103
103
  column: lines[lines.length - 1].length
104
104
  };
105
105
  }
106
+ // ─── Byte → char offset helpers ────────────────────────────────────────────
107
+ /**
108
+ * Builds a lookup table from UTF-8 byte offsets to JavaScript string character
109
+ * indices (UTF-16 code-unit positions).
110
+ *
111
+ * SWC internally represents source as UTF-8 and reports AST spans as byte
112
+ * offsets into that representation. MagicString and all JavaScript String
113
+ * methods operate on UTF-16 code-unit indices. For files that contain only
114
+ * ASCII characters the two coincide, so this function returns `null` as a
115
+ * fast path. For files with multi-byte characters (emoji, accented letters,
116
+ * CJK, etc.) the returned array allows O(1) conversion of any byte offset.
117
+ */
118
+ function buildByteToCharMap(content) {
119
+ // Fast path: pure ASCII means byte offset ≡ char index
120
+ // eslint-disable-next-line no-control-regex
121
+ if (!/[^\x00-\x7F]/.test(content))
122
+ return null;
123
+ const map = [];
124
+ let byteIdx = 0;
125
+ for (let charIdx = 0; charIdx < content.length;) {
126
+ const cp = content.codePointAt(charIdx);
127
+ const byteLen = cp <= 0x7F ? 1 : cp <= 0x7FF ? 2 : cp <= 0xFFFF ? 3 : 4;
128
+ const charLen = cp > 0xFFFF ? 2 : 1; // surrogate pair
129
+ // Every byte belonging to this character maps to the same char index
130
+ for (let b = 0; b < byteLen; b++) {
131
+ map[byteIdx + b] = charIdx;
132
+ }
133
+ byteIdx += byteLen;
134
+ charIdx += charLen;
135
+ }
136
+ // Sentinel so that span.end (one-past-the-last-byte) resolves correctly
137
+ map[byteIdx] = content.length;
138
+ return map;
139
+ }
140
+ /**
141
+ * Recursively converts every `span.start` / `span.end` in an SWC AST from
142
+ * UTF-8 byte offsets to JavaScript string character indices using the
143
+ * pre-built lookup table.
144
+ */
145
+ function convertSpansToCharIndices(node, byteToChar) {
146
+ if (!node || typeof node !== 'object')
147
+ return;
148
+ if (node.span && typeof node.span.start === 'number') {
149
+ const charStart = byteToChar[node.span.start];
150
+ const charEnd = byteToChar[node.span.end];
151
+ if (charStart !== undefined && charEnd !== undefined) {
152
+ node.span = { ...node.span, start: charStart, end: charEnd };
153
+ }
154
+ }
155
+ for (const key of Object.keys(node)) {
156
+ if (key === 'span')
157
+ continue;
158
+ const child = node[key];
159
+ if (Array.isArray(child)) {
160
+ for (const item of child) {
161
+ if (item && typeof item === 'object') {
162
+ convertSpansToCharIndices(item, byteToChar);
163
+ }
164
+ }
165
+ }
166
+ else if (child && typeof child === 'object') {
167
+ convertSpansToCharIndices(child, byteToChar);
168
+ }
169
+ }
170
+ }
171
+ // ─── Ignore-comment helpers ──────────────────────────────────────────────────
172
+ /**
173
+ * Matches the shared ignore directive used by both the instrumenter and the
174
+ * linter. The optional `-next-line` suffix is captured in group 1:
175
+ *
176
+ * i18next-instrument-ignore-next-line → suppress only the single next line
177
+ * i18next-instrument-ignore → suppress the whole next JSX element
178
+ *
179
+ * Works in line-comment (`// ...`) and block-comment form, including the JSX
180
+ * `{ /* ... * / }` form.
181
+ */
182
+ const IGNORE_DIRECTIVE_RE = /i18next-instrument-ignore(-next-line)?/;
183
+ /**
184
+ * Scans `code` for ignore-directive comments and returns a Set of 1-based line
185
+ * numbers whose issues/strings should be suppressed.
186
+ *
187
+ * A directive on line `D` targets the following line `D + 1`. The behaviour
188
+ * depends on the directive variant:
189
+ *
190
+ * - `i18next-instrument-ignore-next-line` suppresses only line `D + 1`.
191
+ * - `i18next-instrument-ignore` suppresses the **entire JSX element** that
192
+ * begins on line `D + 1` — i.e. every line from its opening tag through its
193
+ * closing tag, including nested children. This makes a single directive
194
+ * cover multi-line elements (e.g. `<div … css={…}>…</div>`) and elements
195
+ * with nested children, instead of just the one physical line after it.
196
+ *
197
+ * When no AST node begins on the targeted line (e.g. the next line is a plain
198
+ * `t()` call rather than a JSX element), block scope falls back to suppressing
199
+ * the single targeted line, preserving the original line-based behaviour.
200
+ *
201
+ * `ast` spans must already be normalised to file-relative character indices
202
+ * (see {@link normalizeASTSpans} / {@link convertSpansToCharIndices}).
203
+ */
204
+ function collectIgnoredLineRanges(ast, code) {
205
+ const ignored = new Set();
206
+ const lines = code.split('\n');
207
+ // 1-based target lines, split by directive variant.
208
+ const blockTargets = new Set();
209
+ const lineTargets = new Set();
210
+ for (let i = 0; i < lines.length; i++) {
211
+ const match = IGNORE_DIRECTIVE_RE.exec(lines[i]);
212
+ if (!match)
213
+ continue;
214
+ const target = i + 2; // directive on line i+1 (1-based) → target line i+2
215
+ if (match[1])
216
+ lineTargets.add(target); // `-next-line` variant
217
+ else
218
+ blockTargets.add(target);
219
+ }
220
+ if (blockTargets.size === 0 && lineTargets.size === 0)
221
+ return ignored;
222
+ // Always suppress the directly targeted line. For `-next-line` this is the
223
+ // whole effect; for block directives it is the fallback when no element is
224
+ // found, and otherwise the start of the suppressed range.
225
+ for (const target of lineTargets)
226
+ ignored.add(target);
227
+ for (const target of blockTargets)
228
+ ignored.add(target);
229
+ if (blockTargets.size === 0)
230
+ return ignored;
231
+ // For block directives, find the widest AST node that begins on the targeted
232
+ // line and expand suppression to cover its full line span. Multiple nodes can
233
+ // start on the same line (e.g. a JSXElement and its JSXOpeningElement); taking
234
+ // the largest end line picks the outermost element.
235
+ const widestEndLineByStartLine = new Map();
236
+ const visit = (node) => {
237
+ if (!node || typeof node !== 'object')
238
+ return;
239
+ if (node.span && typeof node.span.start === 'number') {
240
+ const start = lineColumnFromOffset(code, node.span.start);
241
+ if (start && blockTargets.has(start.line)) {
242
+ const end = lineColumnFromOffset(code, node.span.end);
243
+ if (end) {
244
+ const prev = widestEndLineByStartLine.get(start.line);
245
+ if (prev === undefined || end.line > prev) {
246
+ widestEndLineByStartLine.set(start.line, end.line);
247
+ }
248
+ }
249
+ }
250
+ }
251
+ for (const key of Object.keys(node)) {
252
+ if (key === 'span')
253
+ continue;
254
+ const child = node[key];
255
+ if (Array.isArray(child)) {
256
+ for (const item of child)
257
+ visit(item);
258
+ }
259
+ else if (child && typeof child === 'object') {
260
+ visit(child);
261
+ }
262
+ }
263
+ };
264
+ visit(ast);
265
+ for (const target of blockTargets) {
266
+ const endLine = widestEndLineByStartLine.get(target) ?? target;
267
+ for (let line = target; line <= endLine; line++)
268
+ ignored.add(line);
269
+ }
270
+ return ignored;
271
+ }
106
272
  /**
107
273
  * Finds and returns the full property node (KeyValueProperty) for the given
108
274
  * property name from an ObjectExpression.
@@ -189,6 +355,9 @@ function getObjectPropValue(object, propName, identifierResolver) {
189
355
  return undefined;
190
356
  }
191
357
 
358
+ exports.buildByteToCharMap = buildByteToCharMap;
359
+ exports.collectIgnoredLineRanges = collectIgnoredLineRanges;
360
+ exports.convertSpansToCharIndices = convertSpansToCharIndices;
192
361
  exports.findFirstTokenIndex = findFirstTokenIndex;
193
362
  exports.getObjectPropValue = getObjectPropValue;
194
363
  exports.getObjectPropValueExpression = getObjectPropValueExpression;
@@ -262,9 +262,9 @@ async function scanFileForCandidates(content, file, config) {
262
262
  // SWC reports spans as UTF-8 byte offsets, but JavaScript strings and
263
263
  // MagicString use UTF-16 code-unit indices. Without this conversion,
264
264
  // every emoji / accented char / CJK char shifts all subsequent offsets.
265
- const byteToChar = buildByteToCharMap(content);
265
+ const byteToChar = astUtils.buildByteToCharMap(content);
266
266
  if (byteToChar) {
267
- convertSpansToCharIndices(ast, byteToChar);
267
+ astUtils.convertSpansToCharIndices(ast, byteToChar);
268
268
  }
269
269
  // Detect React function component boundaries
270
270
  detectComponentBoundaries(ast, content, components);
@@ -296,11 +296,13 @@ async function scanFileForCandidates(content, file, config) {
296
296
  }
297
297
  // Filter out candidates suppressed by ignore-comment directives.
298
298
  // Supported comments (line or block):
299
- // // i18next-instrument-ignore-next-line
300
- // // i18next-instrument-ignore
299
+ // // i18next-instrument-ignore-next-line → suppress the single next line
300
+ // // i18next-instrument-ignore → suppress the whole next JSX element
301
301
  // /* i18next-instrument-ignore-next-line */
302
302
  // /* i18next-instrument-ignore */
303
- const ignoredLines = collectIgnoredLines(content);
303
+ // AST spans were normalised to char indices above, so the shared helper can
304
+ // resolve element line ranges directly.
305
+ const ignoredLines = astUtils.collectIgnoredLineRanges(ast, content);
304
306
  if (ignoredLines.size > 0) {
305
307
  const keep = [];
306
308
  for (const c of candidates) {
@@ -318,32 +320,6 @@ async function scanFileForCandidates(content, file, config) {
318
320
  }
319
321
  return { candidates, components, languageChangeSites };
320
322
  }
321
- // ─── Ignore-comment helpers ──────────────────────────────────────────────────
322
- /**
323
- * Regex that matches a directive comment requesting the instrumenter to skip
324
- * the **next** line. Works with both line comments (`// ...`) and block
325
- * comments. The supported directives are:
326
- *
327
- * i18next-instrument-ignore-next-line
328
- * i18next-instrument-ignore
329
- */
330
- const IGNORE_RE = /i18next-instrument-ignore(?:-next-line)?/;
331
- /**
332
- * Scans `content` for ignore-directive comments and returns a Set of 1-based
333
- * line numbers whose strings should be excluded from instrumentation.
334
- */
335
- function collectIgnoredLines(content) {
336
- const ignored = new Set();
337
- const lines = content.split('\n');
338
- for (let i = 0; i < lines.length; i++) {
339
- if (IGNORE_RE.test(lines[i])) {
340
- // Directive on line i → suppress the *following* line (i + 1)
341
- // We store 1-based line numbers, so the suppressed line is i + 2
342
- ignored.add(i + 2);
343
- }
344
- }
345
- return ignored;
346
- }
347
323
  /**
348
324
  * Returns the 1-based line number for a character offset.
349
325
  */
@@ -1964,71 +1940,6 @@ async function runInstrumentOnResultPipeline(filePath, initialCandidates, plugin
1964
1940
  }
1965
1941
  return candidates;
1966
1942
  }
1967
- // ─── Byte → char offset helpers ────────────────────────────────────────────
1968
- /**
1969
- * Builds a lookup table from UTF-8 byte offsets to JavaScript string character
1970
- * indices (UTF-16 code-unit positions).
1971
- *
1972
- * SWC internally represents source as UTF-8 and reports AST spans as byte
1973
- * offsets into that representation. MagicString and all JavaScript String
1974
- * methods operate on UTF-16 code-unit indices. For files that contain only
1975
- * ASCII characters the two coincide, so this function returns `null` as a
1976
- * fast path. For files with multi-byte characters (emoji, accented letters,
1977
- * CJK, etc.) the returned array allows O(1) conversion of any byte offset.
1978
- */
1979
- function buildByteToCharMap(content) {
1980
- // Fast path: pure ASCII means byte offset ≡ char index
1981
- // eslint-disable-next-line no-control-regex
1982
- if (!/[^\x00-\x7F]/.test(content))
1983
- return null;
1984
- const map = [];
1985
- let byteIdx = 0;
1986
- for (let charIdx = 0; charIdx < content.length;) {
1987
- const cp = content.codePointAt(charIdx);
1988
- const byteLen = cp <= 0x7F ? 1 : cp <= 0x7FF ? 2 : cp <= 0xFFFF ? 3 : 4;
1989
- const charLen = cp > 0xFFFF ? 2 : 1; // surrogate pair
1990
- // Every byte belonging to this character maps to the same char index
1991
- for (let b = 0; b < byteLen; b++) {
1992
- map[byteIdx + b] = charIdx;
1993
- }
1994
- byteIdx += byteLen;
1995
- charIdx += charLen;
1996
- }
1997
- // Sentinel so that span.end (one-past-the-last-byte) resolves correctly
1998
- map[byteIdx] = content.length;
1999
- return map;
2000
- }
2001
- /**
2002
- * Recursively converts every `span.start` / `span.end` in an SWC AST from
2003
- * UTF-8 byte offsets to JavaScript string character indices using the
2004
- * pre-built lookup table.
2005
- */
2006
- function convertSpansToCharIndices(node, byteToChar) {
2007
- if (!node || typeof node !== 'object')
2008
- return;
2009
- if (node.span && typeof node.span.start === 'number') {
2010
- const charStart = byteToChar[node.span.start];
2011
- const charEnd = byteToChar[node.span.end];
2012
- if (charStart !== undefined && charEnd !== undefined) {
2013
- node.span = { ...node.span, start: charStart, end: charEnd };
2014
- }
2015
- }
2016
- for (const key of Object.keys(node)) {
2017
- if (key === 'span')
2018
- continue;
2019
- const child = node[key];
2020
- if (Array.isArray(child)) {
2021
- for (const item of child) {
2022
- if (item && typeof item === 'object') {
2023
- convertSpansToCharIndices(item, byteToChar);
2024
- }
2025
- }
2026
- }
2027
- else if (child && typeof child === 'object') {
2028
- convertSpansToCharIndices(child, byteToChar);
2029
- }
2030
- }
2031
- }
2032
1943
 
2033
1944
  exports.runInstrumenter = runInstrumenter;
2034
1945
  exports.writeExtractedKeys = writeExtractedKeys;
@@ -9,6 +9,7 @@ var node_util = require('node:util');
9
9
  var logger = require('./utils/logger.js');
10
10
  var wrapOra = require('./utils/wrap-ora.js');
11
11
  var jsxAttributes = require('./utils/jsx-attributes.js');
12
+ var astUtils = require('./extractor/parsers/ast-utils.js');
12
13
 
13
14
  /**
14
15
  * Loads all translation values from the primary locale's JSON files and returns
@@ -101,34 +102,6 @@ function isI18nextOptionKey(key) {
101
102
  return true;
102
103
  return false;
103
104
  }
104
- // ─── Ignore-comment helpers ──────────────────────────────────────────────────
105
- /**
106
- * Regex matching the shared ignore directive used by both the instrumenter and
107
- * the linter. Supports both `-next-line` and inline variants, in line or block
108
- * comment form.
109
- *
110
- * // i18next-instrument-ignore-next-line → suppresses the following line
111
- * // i18next-instrument-ignore → suppresses the following line
112
- * { /* i18next-instrument-ignore * / } → same, block-comment form
113
- */
114
- const LINT_IGNORE_RE = /i18next-instrument-ignore(?:-next-line)?/;
115
- /**
116
- * Scans `code` for ignore-directive comments and returns a Set of 1-based
117
- * line numbers whose issues should be suppressed.
118
- *
119
- * The directive always suppresses the **next** line (line N+1), matching the
120
- * behaviour of the instrumenter's `collectIgnoredLines`.
121
- */
122
- function collectLintIgnoredLines(code) {
123
- const ignored = new Set();
124
- const lines = code.split('\n');
125
- for (let i = 0; i < lines.length; i++) {
126
- if (LINT_IGNORE_RE.test(lines[i])) {
127
- ignored.add(i + 2); // 1-based: directive is on line i+1, suppressed line is i+2
128
- }
129
- }
130
- return ignored;
131
- }
132
105
  // Helper to lint interpolation parameter errors in t() calls
133
106
  function lintInterpolationParams(ast, code, config, translationValues) {
134
107
  const issues = [];
@@ -403,14 +376,30 @@ class Linter extends node_events.EventEmitter {
403
376
  continue;
404
377
  }
405
378
  }
379
+ // Normalise AST spans to file-relative character indices so the ignore
380
+ // directive can resolve the line range of the JSX element it precedes.
381
+ // (findHardcodedStrings/lintInterpolationParams locate issues via text
382
+ // search and don't read spans, so this only affects ignore handling.)
383
+ try {
384
+ const spanBase = ast.span.start - astUtils.findFirstTokenIndex(code);
385
+ astUtils.normalizeASTSpans(ast, spanBase);
386
+ const byteToChar = astUtils.buildByteToCharMap(code);
387
+ if (byteToChar)
388
+ astUtils.convertSpansToCharIndices(ast, byteToChar);
389
+ }
390
+ catch {
391
+ // If span normalisation fails for any reason, fall back to text-based
392
+ // issue detection without block-scoped ignore support.
393
+ }
406
394
  // Collect hardcoded string issues
407
395
  const hardcodedStrings = findHardcodedStrings(ast, code, config);
408
396
  // Collect interpolation parameter issues
409
397
  const interpolationIssues = lintInterpolationParams(ast, code, config, translationValues);
410
398
  let allIssues = [...hardcodedStrings, ...interpolationIssues];
411
399
  // Filter issues suppressed by ignore-directive comments.
412
- // The directive on line N suppresses all issues reported on line N+1.
413
- const ignoredLines = collectLintIgnoredLines(code);
400
+ // i18next-instrument-ignore-next-line suppresses the single next line
401
+ // i18next-instrument-ignore → suppresses the whole next JSX element
402
+ const ignoredLines = astUtils.collectIgnoredLineRanges(ast, code);
414
403
  if (ignoredLines.size > 0) {
415
404
  allIssues = allIssues.filter(issue => !ignoredLines.has(issue.line));
416
405
  }
package/dist/esm/cli.js CHANGED
@@ -30,7 +30,7 @@ const program = new Command();
30
30
  program
31
31
  .name('i18next-cli')
32
32
  .description('A unified, high-performance i18next CLI.')
33
- .version('1.59.1'); // This string is replaced with the actual version at build time by rollup
33
+ .version('1.60.0'); // This string is replaced with the actual version at build time by rollup
34
34
  // new: global config override option
35
35
  program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
36
36
  program
@@ -101,6 +101,172 @@ function lineColumnFromOffset(code, offset) {
101
101
  column: lines[lines.length - 1].length
102
102
  };
103
103
  }
104
+ // ─── Byte → char offset helpers ────────────────────────────────────────────
105
+ /**
106
+ * Builds a lookup table from UTF-8 byte offsets to JavaScript string character
107
+ * indices (UTF-16 code-unit positions).
108
+ *
109
+ * SWC internally represents source as UTF-8 and reports AST spans as byte
110
+ * offsets into that representation. MagicString and all JavaScript String
111
+ * methods operate on UTF-16 code-unit indices. For files that contain only
112
+ * ASCII characters the two coincide, so this function returns `null` as a
113
+ * fast path. For files with multi-byte characters (emoji, accented letters,
114
+ * CJK, etc.) the returned array allows O(1) conversion of any byte offset.
115
+ */
116
+ function buildByteToCharMap(content) {
117
+ // Fast path: pure ASCII means byte offset ≡ char index
118
+ // eslint-disable-next-line no-control-regex
119
+ if (!/[^\x00-\x7F]/.test(content))
120
+ return null;
121
+ const map = [];
122
+ let byteIdx = 0;
123
+ for (let charIdx = 0; charIdx < content.length;) {
124
+ const cp = content.codePointAt(charIdx);
125
+ const byteLen = cp <= 0x7F ? 1 : cp <= 0x7FF ? 2 : cp <= 0xFFFF ? 3 : 4;
126
+ const charLen = cp > 0xFFFF ? 2 : 1; // surrogate pair
127
+ // Every byte belonging to this character maps to the same char index
128
+ for (let b = 0; b < byteLen; b++) {
129
+ map[byteIdx + b] = charIdx;
130
+ }
131
+ byteIdx += byteLen;
132
+ charIdx += charLen;
133
+ }
134
+ // Sentinel so that span.end (one-past-the-last-byte) resolves correctly
135
+ map[byteIdx] = content.length;
136
+ return map;
137
+ }
138
+ /**
139
+ * Recursively converts every `span.start` / `span.end` in an SWC AST from
140
+ * UTF-8 byte offsets to JavaScript string character indices using the
141
+ * pre-built lookup table.
142
+ */
143
+ function convertSpansToCharIndices(node, byteToChar) {
144
+ if (!node || typeof node !== 'object')
145
+ return;
146
+ if (node.span && typeof node.span.start === 'number') {
147
+ const charStart = byteToChar[node.span.start];
148
+ const charEnd = byteToChar[node.span.end];
149
+ if (charStart !== undefined && charEnd !== undefined) {
150
+ node.span = { ...node.span, start: charStart, end: charEnd };
151
+ }
152
+ }
153
+ for (const key of Object.keys(node)) {
154
+ if (key === 'span')
155
+ continue;
156
+ const child = node[key];
157
+ if (Array.isArray(child)) {
158
+ for (const item of child) {
159
+ if (item && typeof item === 'object') {
160
+ convertSpansToCharIndices(item, byteToChar);
161
+ }
162
+ }
163
+ }
164
+ else if (child && typeof child === 'object') {
165
+ convertSpansToCharIndices(child, byteToChar);
166
+ }
167
+ }
168
+ }
169
+ // ─── Ignore-comment helpers ──────────────────────────────────────────────────
170
+ /**
171
+ * Matches the shared ignore directive used by both the instrumenter and the
172
+ * linter. The optional `-next-line` suffix is captured in group 1:
173
+ *
174
+ * i18next-instrument-ignore-next-line → suppress only the single next line
175
+ * i18next-instrument-ignore → suppress the whole next JSX element
176
+ *
177
+ * Works in line-comment (`// ...`) and block-comment form, including the JSX
178
+ * `{ /* ... * / }` form.
179
+ */
180
+ const IGNORE_DIRECTIVE_RE = /i18next-instrument-ignore(-next-line)?/;
181
+ /**
182
+ * Scans `code` for ignore-directive comments and returns a Set of 1-based line
183
+ * numbers whose issues/strings should be suppressed.
184
+ *
185
+ * A directive on line `D` targets the following line `D + 1`. The behaviour
186
+ * depends on the directive variant:
187
+ *
188
+ * - `i18next-instrument-ignore-next-line` suppresses only line `D + 1`.
189
+ * - `i18next-instrument-ignore` suppresses the **entire JSX element** that
190
+ * begins on line `D + 1` — i.e. every line from its opening tag through its
191
+ * closing tag, including nested children. This makes a single directive
192
+ * cover multi-line elements (e.g. `<div … css={…}>…</div>`) and elements
193
+ * with nested children, instead of just the one physical line after it.
194
+ *
195
+ * When no AST node begins on the targeted line (e.g. the next line is a plain
196
+ * `t()` call rather than a JSX element), block scope falls back to suppressing
197
+ * the single targeted line, preserving the original line-based behaviour.
198
+ *
199
+ * `ast` spans must already be normalised to file-relative character indices
200
+ * (see {@link normalizeASTSpans} / {@link convertSpansToCharIndices}).
201
+ */
202
+ function collectIgnoredLineRanges(ast, code) {
203
+ const ignored = new Set();
204
+ const lines = code.split('\n');
205
+ // 1-based target lines, split by directive variant.
206
+ const blockTargets = new Set();
207
+ const lineTargets = new Set();
208
+ for (let i = 0; i < lines.length; i++) {
209
+ const match = IGNORE_DIRECTIVE_RE.exec(lines[i]);
210
+ if (!match)
211
+ continue;
212
+ const target = i + 2; // directive on line i+1 (1-based) → target line i+2
213
+ if (match[1])
214
+ lineTargets.add(target); // `-next-line` variant
215
+ else
216
+ blockTargets.add(target);
217
+ }
218
+ if (blockTargets.size === 0 && lineTargets.size === 0)
219
+ return ignored;
220
+ // Always suppress the directly targeted line. For `-next-line` this is the
221
+ // whole effect; for block directives it is the fallback when no element is
222
+ // found, and otherwise the start of the suppressed range.
223
+ for (const target of lineTargets)
224
+ ignored.add(target);
225
+ for (const target of blockTargets)
226
+ ignored.add(target);
227
+ if (blockTargets.size === 0)
228
+ return ignored;
229
+ // For block directives, find the widest AST node that begins on the targeted
230
+ // line and expand suppression to cover its full line span. Multiple nodes can
231
+ // start on the same line (e.g. a JSXElement and its JSXOpeningElement); taking
232
+ // the largest end line picks the outermost element.
233
+ const widestEndLineByStartLine = new Map();
234
+ const visit = (node) => {
235
+ if (!node || typeof node !== 'object')
236
+ return;
237
+ if (node.span && typeof node.span.start === 'number') {
238
+ const start = lineColumnFromOffset(code, node.span.start);
239
+ if (start && blockTargets.has(start.line)) {
240
+ const end = lineColumnFromOffset(code, node.span.end);
241
+ if (end) {
242
+ const prev = widestEndLineByStartLine.get(start.line);
243
+ if (prev === undefined || end.line > prev) {
244
+ widestEndLineByStartLine.set(start.line, end.line);
245
+ }
246
+ }
247
+ }
248
+ }
249
+ for (const key of Object.keys(node)) {
250
+ if (key === 'span')
251
+ continue;
252
+ const child = node[key];
253
+ if (Array.isArray(child)) {
254
+ for (const item of child)
255
+ visit(item);
256
+ }
257
+ else if (child && typeof child === 'object') {
258
+ visit(child);
259
+ }
260
+ }
261
+ };
262
+ visit(ast);
263
+ for (const target of blockTargets) {
264
+ const endLine = widestEndLineByStartLine.get(target) ?? target;
265
+ for (let line = target; line <= endLine; line++)
266
+ ignored.add(line);
267
+ }
268
+ return ignored;
269
+ }
104
270
  /**
105
271
  * Finds and returns the full property node (KeyValueProperty) for the given
106
272
  * property name from an ObjectExpression.
@@ -187,4 +353,4 @@ function getObjectPropValue(object, propName, identifierResolver) {
187
353
  return undefined;
188
354
  }
189
355
 
190
- export { findFirstTokenIndex, getObjectPropValue, getObjectPropValueExpression, getObjectProperty, isSimpleTemplateLiteral, lineColumnFromOffset, normalizeASTSpans };
356
+ export { buildByteToCharMap, collectIgnoredLineRanges, convertSpansToCharIndices, findFirstTokenIndex, getObjectPropValue, getObjectPropValueExpression, getObjectProperty, isSimpleTemplateLiteral, lineColumnFromOffset, normalizeASTSpans };
@@ -10,7 +10,7 @@ import { createKeyRegistry, generateKeyFromContent } from './key-generator.js';
10
10
  import { createSpinnerLike } from '../../utils/wrap-ora.js';
11
11
  import { ConsoleLogger } from '../../utils/logger.js';
12
12
  import { ignoredAttributeSet } from '../../utils/jsx-attributes.js';
13
- import { findFirstTokenIndex, normalizeASTSpans } from '../../extractor/parsers/ast-utils.js';
13
+ import { findFirstTokenIndex, normalizeASTSpans, buildByteToCharMap, convertSpansToCharIndices, collectIgnoredLineRanges } from '../../extractor/parsers/ast-utils.js';
14
14
  import { getOutputPath } from '../../utils/file-utils.js';
15
15
 
16
16
  /**
@@ -294,11 +294,13 @@ async function scanFileForCandidates(content, file, config) {
294
294
  }
295
295
  // Filter out candidates suppressed by ignore-comment directives.
296
296
  // Supported comments (line or block):
297
- // // i18next-instrument-ignore-next-line
298
- // // i18next-instrument-ignore
297
+ // // i18next-instrument-ignore-next-line → suppress the single next line
298
+ // // i18next-instrument-ignore → suppress the whole next JSX element
299
299
  // /* i18next-instrument-ignore-next-line */
300
300
  // /* i18next-instrument-ignore */
301
- const ignoredLines = collectIgnoredLines(content);
301
+ // AST spans were normalised to char indices above, so the shared helper can
302
+ // resolve element line ranges directly.
303
+ const ignoredLines = collectIgnoredLineRanges(ast, content);
302
304
  if (ignoredLines.size > 0) {
303
305
  const keep = [];
304
306
  for (const c of candidates) {
@@ -316,32 +318,6 @@ async function scanFileForCandidates(content, file, config) {
316
318
  }
317
319
  return { candidates, components, languageChangeSites };
318
320
  }
319
- // ─── Ignore-comment helpers ──────────────────────────────────────────────────
320
- /**
321
- * Regex that matches a directive comment requesting the instrumenter to skip
322
- * the **next** line. Works with both line comments (`// ...`) and block
323
- * comments. The supported directives are:
324
- *
325
- * i18next-instrument-ignore-next-line
326
- * i18next-instrument-ignore
327
- */
328
- const IGNORE_RE = /i18next-instrument-ignore(?:-next-line)?/;
329
- /**
330
- * Scans `content` for ignore-directive comments and returns a Set of 1-based
331
- * line numbers whose strings should be excluded from instrumentation.
332
- */
333
- function collectIgnoredLines(content) {
334
- const ignored = new Set();
335
- const lines = content.split('\n');
336
- for (let i = 0; i < lines.length; i++) {
337
- if (IGNORE_RE.test(lines[i])) {
338
- // Directive on line i → suppress the *following* line (i + 1)
339
- // We store 1-based line numbers, so the suppressed line is i + 2
340
- ignored.add(i + 2);
341
- }
342
- }
343
- return ignored;
344
- }
345
321
  /**
346
322
  * Returns the 1-based line number for a character offset.
347
323
  */
@@ -1962,70 +1938,5 @@ async function runInstrumentOnResultPipeline(filePath, initialCandidates, plugin
1962
1938
  }
1963
1939
  return candidates;
1964
1940
  }
1965
- // ─── Byte → char offset helpers ────────────────────────────────────────────
1966
- /**
1967
- * Builds a lookup table from UTF-8 byte offsets to JavaScript string character
1968
- * indices (UTF-16 code-unit positions).
1969
- *
1970
- * SWC internally represents source as UTF-8 and reports AST spans as byte
1971
- * offsets into that representation. MagicString and all JavaScript String
1972
- * methods operate on UTF-16 code-unit indices. For files that contain only
1973
- * ASCII characters the two coincide, so this function returns `null` as a
1974
- * fast path. For files with multi-byte characters (emoji, accented letters,
1975
- * CJK, etc.) the returned array allows O(1) conversion of any byte offset.
1976
- */
1977
- function buildByteToCharMap(content) {
1978
- // Fast path: pure ASCII means byte offset ≡ char index
1979
- // eslint-disable-next-line no-control-regex
1980
- if (!/[^\x00-\x7F]/.test(content))
1981
- return null;
1982
- const map = [];
1983
- let byteIdx = 0;
1984
- for (let charIdx = 0; charIdx < content.length;) {
1985
- const cp = content.codePointAt(charIdx);
1986
- const byteLen = cp <= 0x7F ? 1 : cp <= 0x7FF ? 2 : cp <= 0xFFFF ? 3 : 4;
1987
- const charLen = cp > 0xFFFF ? 2 : 1; // surrogate pair
1988
- // Every byte belonging to this character maps to the same char index
1989
- for (let b = 0; b < byteLen; b++) {
1990
- map[byteIdx + b] = charIdx;
1991
- }
1992
- byteIdx += byteLen;
1993
- charIdx += charLen;
1994
- }
1995
- // Sentinel so that span.end (one-past-the-last-byte) resolves correctly
1996
- map[byteIdx] = content.length;
1997
- return map;
1998
- }
1999
- /**
2000
- * Recursively converts every `span.start` / `span.end` in an SWC AST from
2001
- * UTF-8 byte offsets to JavaScript string character indices using the
2002
- * pre-built lookup table.
2003
- */
2004
- function convertSpansToCharIndices(node, byteToChar) {
2005
- if (!node || typeof node !== 'object')
2006
- return;
2007
- if (node.span && typeof node.span.start === 'number') {
2008
- const charStart = byteToChar[node.span.start];
2009
- const charEnd = byteToChar[node.span.end];
2010
- if (charStart !== undefined && charEnd !== undefined) {
2011
- node.span = { ...node.span, start: charStart, end: charEnd };
2012
- }
2013
- }
2014
- for (const key of Object.keys(node)) {
2015
- if (key === 'span')
2016
- continue;
2017
- const child = node[key];
2018
- if (Array.isArray(child)) {
2019
- for (const item of child) {
2020
- if (item && typeof item === 'object') {
2021
- convertSpansToCharIndices(item, byteToChar);
2022
- }
2023
- }
2024
- }
2025
- else if (child && typeof child === 'object') {
2026
- convertSpansToCharIndices(child, byteToChar);
2027
- }
2028
- }
2029
- }
2030
1941
 
2031
1942
  export { runInstrumenter, writeExtractedKeys };
@@ -7,6 +7,7 @@ import { styleText } from 'node:util';
7
7
  import { ConsoleLogger } from './utils/logger.js';
8
8
  import { createSpinnerLike } from './utils/wrap-ora.js';
9
9
  import { acceptedTags, translatableAttributes, ignoredTags, ignoredAttributeLowerSet } from './utils/jsx-attributes.js';
10
+ import { findFirstTokenIndex, normalizeASTSpans, buildByteToCharMap, convertSpansToCharIndices, collectIgnoredLineRanges } from './extractor/parsers/ast-utils.js';
10
11
 
11
12
  /**
12
13
  * Loads all translation values from the primary locale's JSON files and returns
@@ -99,34 +100,6 @@ function isI18nextOptionKey(key) {
99
100
  return true;
100
101
  return false;
101
102
  }
102
- // ─── Ignore-comment helpers ──────────────────────────────────────────────────
103
- /**
104
- * Regex matching the shared ignore directive used by both the instrumenter and
105
- * the linter. Supports both `-next-line` and inline variants, in line or block
106
- * comment form.
107
- *
108
- * // i18next-instrument-ignore-next-line → suppresses the following line
109
- * // i18next-instrument-ignore → suppresses the following line
110
- * { /* i18next-instrument-ignore * / } → same, block-comment form
111
- */
112
- const LINT_IGNORE_RE = /i18next-instrument-ignore(?:-next-line)?/;
113
- /**
114
- * Scans `code` for ignore-directive comments and returns a Set of 1-based
115
- * line numbers whose issues should be suppressed.
116
- *
117
- * The directive always suppresses the **next** line (line N+1), matching the
118
- * behaviour of the instrumenter's `collectIgnoredLines`.
119
- */
120
- function collectLintIgnoredLines(code) {
121
- const ignored = new Set();
122
- const lines = code.split('\n');
123
- for (let i = 0; i < lines.length; i++) {
124
- if (LINT_IGNORE_RE.test(lines[i])) {
125
- ignored.add(i + 2); // 1-based: directive is on line i+1, suppressed line is i+2
126
- }
127
- }
128
- return ignored;
129
- }
130
103
  // Helper to lint interpolation parameter errors in t() calls
131
104
  function lintInterpolationParams(ast, code, config, translationValues) {
132
105
  const issues = [];
@@ -401,14 +374,30 @@ class Linter extends EventEmitter {
401
374
  continue;
402
375
  }
403
376
  }
377
+ // Normalise AST spans to file-relative character indices so the ignore
378
+ // directive can resolve the line range of the JSX element it precedes.
379
+ // (findHardcodedStrings/lintInterpolationParams locate issues via text
380
+ // search and don't read spans, so this only affects ignore handling.)
381
+ try {
382
+ const spanBase = ast.span.start - findFirstTokenIndex(code);
383
+ normalizeASTSpans(ast, spanBase);
384
+ const byteToChar = buildByteToCharMap(code);
385
+ if (byteToChar)
386
+ convertSpansToCharIndices(ast, byteToChar);
387
+ }
388
+ catch {
389
+ // If span normalisation fails for any reason, fall back to text-based
390
+ // issue detection without block-scoped ignore support.
391
+ }
404
392
  // Collect hardcoded string issues
405
393
  const hardcodedStrings = findHardcodedStrings(ast, code, config);
406
394
  // Collect interpolation parameter issues
407
395
  const interpolationIssues = lintInterpolationParams(ast, code, config, translationValues);
408
396
  let allIssues = [...hardcodedStrings, ...interpolationIssues];
409
397
  // Filter issues suppressed by ignore-directive comments.
410
- // The directive on line N suppresses all issues reported on line N+1.
411
- const ignoredLines = collectLintIgnoredLines(code);
398
+ // i18next-instrument-ignore-next-line suppresses the single next line
399
+ // i18next-instrument-ignore → suppresses the whole next JSX element
400
+ const ignoredLines = collectIgnoredLineRanges(ast, code);
412
401
  if (ignoredLines.size > 0) {
413
402
  allIssues = allIssues.filter(issue => !ignoredLines.has(issue.line));
414
403
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "1.59.1",
3
+ "version": "1.60.0",
4
4
  "description": "A unified, high-performance i18next CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -36,6 +36,46 @@ export declare function lineColumnFromOffset(code: string, offset: number): {
36
36
  line: number;
37
37
  column: number;
38
38
  } | undefined;
39
+ /**
40
+ * Builds a lookup table from UTF-8 byte offsets to JavaScript string character
41
+ * indices (UTF-16 code-unit positions).
42
+ *
43
+ * SWC internally represents source as UTF-8 and reports AST spans as byte
44
+ * offsets into that representation. MagicString and all JavaScript String
45
+ * methods operate on UTF-16 code-unit indices. For files that contain only
46
+ * ASCII characters the two coincide, so this function returns `null` as a
47
+ * fast path. For files with multi-byte characters (emoji, accented letters,
48
+ * CJK, etc.) the returned array allows O(1) conversion of any byte offset.
49
+ */
50
+ export declare function buildByteToCharMap(content: string): number[] | null;
51
+ /**
52
+ * Recursively converts every `span.start` / `span.end` in an SWC AST from
53
+ * UTF-8 byte offsets to JavaScript string character indices using the
54
+ * pre-built lookup table.
55
+ */
56
+ export declare function convertSpansToCharIndices(node: any, byteToChar: number[]): void;
57
+ /**
58
+ * Scans `code` for ignore-directive comments and returns a Set of 1-based line
59
+ * numbers whose issues/strings should be suppressed.
60
+ *
61
+ * A directive on line `D` targets the following line `D + 1`. The behaviour
62
+ * depends on the directive variant:
63
+ *
64
+ * - `i18next-instrument-ignore-next-line` suppresses only line `D + 1`.
65
+ * - `i18next-instrument-ignore` suppresses the **entire JSX element** that
66
+ * begins on line `D + 1` — i.e. every line from its opening tag through its
67
+ * closing tag, including nested children. This makes a single directive
68
+ * cover multi-line elements (e.g. `<div … css={…}>…</div>`) and elements
69
+ * with nested children, instead of just the one physical line after it.
70
+ *
71
+ * When no AST node begins on the targeted line (e.g. the next line is a plain
72
+ * `t()` call rather than a JSX element), block scope falls back to suppressing
73
+ * the single targeted line, preserving the original line-based behaviour.
74
+ *
75
+ * `ast` spans must already be normalised to file-relative character indices
76
+ * (see {@link normalizeASTSpans} / {@link convertSpansToCharIndices}).
77
+ */
78
+ export declare function collectIgnoredLineRanges(ast: any, code: string): Set<number>;
39
79
  /**
40
80
  * Finds and returns the full property node (KeyValueProperty) for the given
41
81
  * property name from an ObjectExpression.
@@ -1 +1 @@
1
- {"version":3,"file":"ast-utils.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/ast-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAc,gBAAgB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAE1F;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CA2BzD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CA0BhE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAQhH;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,qDAU5E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,4BAA4B,CAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAKhH;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAE1E;AAED,KAAK,kBAAkB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;AAEjF;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,EAAE,kBAAkB,CAAC,EAAE,kBAAkB,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAkB9J"}
1
+ {"version":3,"file":"ast-utils.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/ast-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAc,gBAAgB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAE1F;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CA2BzD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CA0BhE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAQhH;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CA0BpE;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAE,IAAI,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,IAAI,CAwBhF;AAgBD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,wBAAwB,CAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CA4D7E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,qDAU5E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,4BAA4B,CAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAKhH;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAE1E;AAED,KAAK,kBAAkB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;AAEjF;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,EAAE,kBAAkB,CAAC,EAAE,kBAAkB,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAkB9J"}
@@ -1 +1 @@
1
- {"version":3,"file":"instrumenter.d.ts","sourceRoot":"","sources":["../../../src/instrumenter/core/instrumenter.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,eAAe,EAA6B,sBAAsB,EAAiE,MAAM,gBAAgB,CAAA;AAU1N;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,EAAE,mBAAmB,EAC5B,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,sBAAsB,CAAC,CAoNjC;AA4wDD;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,eAAe,EAAE,EAC7B,MAAM,EAAE,oBAAoB,EAC5B,SAAS,CAAC,EAAE,MAAM,EAClB,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,IAAI,CAAC,CAoDf"}
1
+ {"version":3,"file":"instrumenter.d.ts","sourceRoot":"","sources":["../../../src/instrumenter/core/instrumenter.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,eAAe,EAA6B,sBAAsB,EAAiE,MAAM,gBAAgB,CAAA;AAU1N;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,EAAE,mBAAmB,EAC5B,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,sBAAsB,CAAC,CAoNjC;AAivDD;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,eAAe,EAAE,EAC7B,MAAM,EAAE,oBAAoB,EAC5B,SAAS,CAAC,EAAE,MAAM,EAClB,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,IAAI,CAAC,CAoDf"}
@@ -1 +1 @@
1
- {"version":3,"file":"linter.d.ts","sourceRoot":"","sources":["../src/linter.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAK1C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAE,SAAS,EAA6B,MAAM,YAAY,CAAA;AA8SpG,KAAK,cAAc,GAAG;IACpB,QAAQ,EAAE;QAAC;YACT,OAAO,EAAE,MAAM,CAAC;SACjB;KAAC,CAAC;IACH,IAAI,EAAE;QAAC;YACL,OAAO,EAAE,OAAO,CAAC;YACjB,OAAO,EAAE,MAAM,CAAC;YAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;SACpC;KAAC,CAAC;IACH,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;CACvB,CAAA;AAED,eAAO,MAAM,uBAAuB,EAAE,MAAM,EAAiD,CAAA;AAC7F,eAAO,MAAM,6BAA6B,EAAE,MAAM,EAAqD,CAAA;AAKvG,qBAAa,MAAO,SAAQ,YAAY,CAAC,cAAc,CAAC;IACtD,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,MAAM,CAAQ;gBAET,MAAM,EAAE,oBAAoB,EAAE,MAAM,GAAE,MAA4B;IAM/E,SAAS,CAAE,KAAK,EAAE,OAAO;IAanB,GAAG;;;;;;;IAmHT,OAAO,CAAC,uBAAuB;YAOjB,qBAAqB;IAWnC,OAAO,CAAC,kBAAkB;IAM1B,OAAO,CAAC,0BAA0B;YAUpB,qBAAqB;YAgBrB,uBAAuB;CAgBtC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,SAAS,CAAE,MAAM,EAAE,oBAAoB;;;;;;GAE5D;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,iBAkCnD"}
1
+ {"version":3,"file":"linter.d.ts","sourceRoot":"","sources":["../src/linter.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAM1C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAE,SAAS,EAA6B,MAAM,YAAY,CAAA;AA+QpG,KAAK,cAAc,GAAG;IACpB,QAAQ,EAAE;QAAC;YACT,OAAO,EAAE,MAAM,CAAC;SACjB;KAAC,CAAC;IACH,IAAI,EAAE;QAAC;YACL,OAAO,EAAE,OAAO,CAAC;YACjB,OAAO,EAAE,MAAM,CAAC;YAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;SACpC;KAAC,CAAC;IACH,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;CACvB,CAAA;AAED,eAAO,MAAM,uBAAuB,EAAE,MAAM,EAAiD,CAAA;AAC7F,eAAO,MAAM,6BAA6B,EAAE,MAAM,EAAqD,CAAA;AAKvG,qBAAa,MAAO,SAAQ,YAAY,CAAC,cAAc,CAAC;IACtD,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,MAAM,CAAQ;gBAET,MAAM,EAAE,oBAAoB,EAAE,MAAM,GAAE,MAA4B;IAM/E,SAAS,CAAE,KAAK,EAAE,OAAO;IAanB,GAAG;;;;;;;IAkIT,OAAO,CAAC,uBAAuB;YAOjB,qBAAqB;IAWnC,OAAO,CAAC,kBAAkB;IAM1B,OAAO,CAAC,0BAA0B;YAUpB,qBAAqB;YAgBrB,uBAAuB;CAgBtC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,SAAS,CAAE,MAAM,EAAE,oBAAoB;;;;;;GAE5D;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,iBAkCnD"}