@tanstack/start-plugin-core 1.161.3 → 1.162.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 (32) hide show
  1. package/dist/esm/import-protection-plugin/defaults.d.ts +6 -4
  2. package/dist/esm/import-protection-plugin/defaults.js +3 -12
  3. package/dist/esm/import-protection-plugin/defaults.js.map +1 -1
  4. package/dist/esm/import-protection-plugin/plugin.d.ts +1 -1
  5. package/dist/esm/import-protection-plugin/plugin.js +488 -257
  6. package/dist/esm/import-protection-plugin/plugin.js.map +1 -1
  7. package/dist/esm/import-protection-plugin/postCompileUsage.d.ts +4 -2
  8. package/dist/esm/import-protection-plugin/postCompileUsage.js +31 -150
  9. package/dist/esm/import-protection-plugin/postCompileUsage.js.map +1 -1
  10. package/dist/esm/import-protection-plugin/rewriteDeniedImports.js +13 -9
  11. package/dist/esm/import-protection-plugin/rewriteDeniedImports.js.map +1 -1
  12. package/dist/esm/import-protection-plugin/sourceLocation.d.ts +32 -66
  13. package/dist/esm/import-protection-plugin/sourceLocation.js +129 -56
  14. package/dist/esm/import-protection-plugin/sourceLocation.js.map +1 -1
  15. package/dist/esm/import-protection-plugin/trace.d.ts +10 -0
  16. package/dist/esm/import-protection-plugin/trace.js +30 -44
  17. package/dist/esm/import-protection-plugin/trace.js.map +1 -1
  18. package/dist/esm/import-protection-plugin/utils.d.ts +8 -4
  19. package/dist/esm/import-protection-plugin/utils.js +43 -1
  20. package/dist/esm/import-protection-plugin/utils.js.map +1 -1
  21. package/dist/esm/import-protection-plugin/virtualModules.d.ts +7 -1
  22. package/dist/esm/import-protection-plugin/virtualModules.js +104 -135
  23. package/dist/esm/import-protection-plugin/virtualModules.js.map +1 -1
  24. package/package.json +8 -8
  25. package/src/import-protection-plugin/defaults.ts +8 -19
  26. package/src/import-protection-plugin/plugin.ts +776 -433
  27. package/src/import-protection-plugin/postCompileUsage.ts +57 -229
  28. package/src/import-protection-plugin/rewriteDeniedImports.ts +34 -42
  29. package/src/import-protection-plugin/sourceLocation.ts +184 -185
  30. package/src/import-protection-plugin/trace.ts +38 -49
  31. package/src/import-protection-plugin/utils.ts +62 -1
  32. package/src/import-protection-plugin/virtualModules.ts +163 -177
@@ -1 +1 @@
1
- {"version":3,"file":"rewriteDeniedImports.js","sources":["../../../src/import-protection-plugin/rewriteDeniedImports.ts"],"sourcesContent":["import * as t from '@babel/types'\nimport { generateFromAst, parseAst } from '@tanstack/router-utils'\n\nimport { MOCK_MODULE_ID } from './virtualModules'\n\n// ---------------------------------------------------------------------------\n// Export name collection (for dev mock-edge modules)\n// ---------------------------------------------------------------------------\n\nexport function isValidExportName(name: string): boolean {\n if (name === 'default') return false\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name)\n}\n\n/**\n * Best-effort static analysis of an importer's source to determine which\n * named exports are needed per specifier, to keep native ESM valid in dev.\n */\nexport function collectMockExportNamesBySource(\n code: string,\n): Map<string, Array<string>> {\n const ast = parseAst({ code })\n\n const namesBySource = new Map<string, Set<string>>()\n const add = (source: string, name: string) => {\n if (!isValidExportName(name)) return\n let set = namesBySource.get(source)\n if (!set) {\n set = new Set<string>()\n namesBySource.set(source, set)\n }\n set.add(name)\n }\n\n for (const node of ast.program.body) {\n if (t.isImportDeclaration(node)) {\n if (node.importKind === 'type') continue\n const source = node.source.value\n for (const s of node.specifiers) {\n if (!t.isImportSpecifier(s)) continue\n if (s.importKind === 'type') continue\n const importedName = t.isIdentifier(s.imported)\n ? s.imported.name\n : s.imported.value\n // `import { default as x } from 'm'` only requires a default export.\n if (importedName === 'default') continue\n add(source, importedName)\n }\n }\n\n if (t.isExportNamedDeclaration(node) && node.source?.value) {\n if (node.exportKind === 'type') continue\n const source = node.source.value\n for (const s of node.specifiers) {\n if (!t.isExportSpecifier(s)) continue\n if (s.exportKind === 'type') continue\n add(source, s.local.name)\n }\n }\n }\n\n const out = new Map<string, Array<string>>()\n for (const [source, set] of namesBySource) {\n out.set(source, Array.from(set).sort())\n }\n return out\n}\n\n// ---------------------------------------------------------------------------\n// AST-based import rewriting\n// ---------------------------------------------------------------------------\n\n/**\n * Rewrite static imports/re-exports from denied sources using Babel AST transforms.\n *\n * Transforms:\n * import { a as b, c } from 'denied'\n * Into:\n * import __tss_deny_0 from 'tanstack-start-import-protection:mock'\n * const b = __tss_deny_0.a\n * const c = __tss_deny_0.c\n *\n * Also handles:\n * import def from 'denied' -> import def from mock\n * import * as ns from 'denied' -> import ns from mock\n * export { x } from 'denied' -> export const x = mock.x\n * export * from 'denied' -> removed\n * export { x as y } from 'denied' -> export const y = mock.x\n */\nexport function rewriteDeniedImports(\n code: string,\n id: string,\n deniedSources: Set<string>,\n getMockModuleId: (source: string) => string = () => MOCK_MODULE_ID,\n): { code: string; map?: object | null } | undefined {\n const ast = parseAst({ code })\n let modified = false\n let mockCounter = 0\n\n // Walk program body in reverse so splice indices stay valid\n for (let i = ast.program.body.length - 1; i >= 0; i--) {\n const node = ast.program.body[i]!\n\n // --- import declarations ---\n if (t.isImportDeclaration(node)) {\n // Skip type-only imports\n if (node.importKind === 'type') continue\n if (!deniedSources.has(node.source.value)) continue\n\n const mockVar = `__tss_deny_${mockCounter++}`\n const replacements: Array<t.Statement> = []\n\n // import __tss_deny_N from '<mock>'\n replacements.push(\n t.importDeclaration(\n [t.importDefaultSpecifier(t.identifier(mockVar))],\n t.stringLiteral(getMockModuleId(node.source.value)),\n ),\n )\n\n for (const specifier of node.specifiers) {\n if (t.isImportDefaultSpecifier(specifier)) {\n // import def from 'denied' -> const def = __tss_deny_N\n replacements.push(\n t.variableDeclaration('const', [\n t.variableDeclarator(\n t.identifier(specifier.local.name),\n t.identifier(mockVar),\n ),\n ]),\n )\n } else if (t.isImportNamespaceSpecifier(specifier)) {\n // import * as ns from 'denied' -> const ns = __tss_deny_N\n replacements.push(\n t.variableDeclaration('const', [\n t.variableDeclarator(\n t.identifier(specifier.local.name),\n t.identifier(mockVar),\n ),\n ]),\n )\n } else if (t.isImportSpecifier(specifier)) {\n // Skip type-only specifiers\n if (specifier.importKind === 'type') continue\n // import { a as b } from 'denied' -> const b = __tss_deny_N.a\n const importedName = t.isIdentifier(specifier.imported)\n ? specifier.imported.name\n : specifier.imported.value\n replacements.push(\n t.variableDeclaration('const', [\n t.variableDeclarator(\n t.identifier(specifier.local.name),\n t.memberExpression(\n t.identifier(mockVar),\n t.identifier(importedName),\n ),\n ),\n ]),\n )\n }\n }\n\n ast.program.body.splice(i, 1, ...replacements)\n modified = true\n continue\n }\n\n // --- export { x } from 'denied' ---\n if (t.isExportNamedDeclaration(node) && node.source) {\n if (node.exportKind === 'type') continue\n if (!deniedSources.has(node.source.value)) continue\n\n const mockVar = `__tss_deny_${mockCounter++}`\n const replacements: Array<t.Statement> = []\n\n // import __tss_deny_N from '<mock>'\n replacements.push(\n t.importDeclaration(\n [t.importDefaultSpecifier(t.identifier(mockVar))],\n t.stringLiteral(getMockModuleId(node.source.value)),\n ),\n )\n\n // For each re-exported specifier, create an exported const\n const exportSpecifiers: Array<{\n localName: string\n exportedName: string\n }> = []\n for (const specifier of node.specifiers) {\n if (t.isExportSpecifier(specifier)) {\n if (specifier.exportKind === 'type') continue\n const localName = specifier.local.name\n const exportedName = t.isIdentifier(specifier.exported)\n ? specifier.exported.name\n : specifier.exported.value\n\n const internalVar = `__tss_reexport_${localName}`\n // const __tss_reexport_x = __tss_deny_N.x\n replacements.push(\n t.variableDeclaration('const', [\n t.variableDeclarator(\n t.identifier(internalVar),\n t.memberExpression(\n t.identifier(mockVar),\n t.identifier(localName),\n ),\n ),\n ]),\n )\n exportSpecifiers.push({ localName: internalVar, exportedName })\n }\n }\n\n // export { __tss_reexport_x as x, ... }\n if (exportSpecifiers.length > 0) {\n replacements.push(\n t.exportNamedDeclaration(\n null,\n exportSpecifiers.map((s) =>\n t.exportSpecifier(\n t.identifier(s.localName),\n t.identifier(s.exportedName),\n ),\n ),\n ),\n )\n }\n\n ast.program.body.splice(i, 1, ...replacements)\n modified = true\n continue\n }\n\n // --- export * from 'denied' ---\n if (t.isExportAllDeclaration(node)) {\n if (node.exportKind === 'type') continue\n if (!deniedSources.has(node.source.value)) continue\n\n // Remove the star re-export entirely\n ast.program.body.splice(i, 1)\n modified = true\n continue\n }\n }\n\n if (!modified) return undefined\n\n const result = generateFromAst(ast, {\n sourceMaps: true,\n sourceFileName: id,\n filename: id,\n })\n\n return { code: result.code, map: result.map }\n}\n"],"names":[],"mappings":";;AASO,SAAS,kBAAkB,MAAuB;AACvD,MAAI,SAAS,UAAW,QAAO;AAC/B,SAAO,6BAA6B,KAAK,IAAI;AAC/C;AAMO,SAAS,+BACd,MAC4B;AAC5B,QAAM,MAAM,SAAS,EAAE,MAAM;AAE7B,QAAM,oCAAoB,IAAA;AAC1B,QAAM,MAAM,CAAC,QAAgB,SAAiB;AAC5C,QAAI,CAAC,kBAAkB,IAAI,EAAG;AAC9B,QAAI,MAAM,cAAc,IAAI,MAAM;AAClC,QAAI,CAAC,KAAK;AACR,gCAAU,IAAA;AACV,oBAAc,IAAI,QAAQ,GAAG;AAAA,IAC/B;AACA,QAAI,IAAI,IAAI;AAAA,EACd;AAEA,aAAW,QAAQ,IAAI,QAAQ,MAAM;AACnC,QAAI,EAAE,oBAAoB,IAAI,GAAG;AAC/B,UAAI,KAAK,eAAe,OAAQ;AAChC,YAAM,SAAS,KAAK,OAAO;AAC3B,iBAAW,KAAK,KAAK,YAAY;AAC/B,YAAI,CAAC,EAAE,kBAAkB,CAAC,EAAG;AAC7B,YAAI,EAAE,eAAe,OAAQ;AAC7B,cAAM,eAAe,EAAE,aAAa,EAAE,QAAQ,IAC1C,EAAE,SAAS,OACX,EAAE,SAAS;AAEf,YAAI,iBAAiB,UAAW;AAChC,YAAI,QAAQ,YAAY;AAAA,MAC1B;AAAA,IACF;AAEA,QAAI,EAAE,yBAAyB,IAAI,KAAK,KAAK,QAAQ,OAAO;AAC1D,UAAI,KAAK,eAAe,OAAQ;AAChC,YAAM,SAAS,KAAK,OAAO;AAC3B,iBAAW,KAAK,KAAK,YAAY;AAC/B,YAAI,CAAC,EAAE,kBAAkB,CAAC,EAAG;AAC7B,YAAI,EAAE,eAAe,OAAQ;AAC7B,YAAI,QAAQ,EAAE,MAAM,IAAI;AAAA,MAC1B;AAAA,IACF;AAAA,EACF;AAEA,QAAM,0BAAU,IAAA;AAChB,aAAW,CAAC,QAAQ,GAAG,KAAK,eAAe;AACzC,QAAI,IAAI,QAAQ,MAAM,KAAK,GAAG,EAAE,MAAM;AAAA,EACxC;AACA,SAAO;AACT;"}
1
+ {"version":3,"file":"rewriteDeniedImports.js","sources":["../../../src/import-protection-plugin/rewriteDeniedImports.ts"],"sourcesContent":["import * as t from '@babel/types'\nimport { generateFromAst, parseAst } from '@tanstack/router-utils'\n\nimport { MOCK_MODULE_ID } from './virtualModules'\nimport { getOrCreate } from './utils'\n\nexport function isValidExportName(name: string): boolean {\n if (name === 'default' || name.length === 0) return false\n const first = name.charCodeAt(0)\n // First char: A-Z (65-90), a-z (97-122), _ (95), $ (36)\n if (\n !(\n (first >= 65 && first <= 90) ||\n (first >= 97 && first <= 122) ||\n first === 95 ||\n first === 36\n )\n )\n return false\n for (let i = 1; i < name.length; i++) {\n const ch = name.charCodeAt(i)\n // Subsequent: A-Z, a-z, 0-9 (48-57), _, $\n if (\n !(\n (ch >= 65 && ch <= 90) ||\n (ch >= 97 && ch <= 122) ||\n (ch >= 48 && ch <= 57) ||\n ch === 95 ||\n ch === 36\n )\n )\n return false\n }\n return true\n}\n\n/**\n * Best-effort static analysis of an importer's source to determine which\n * named exports are needed per specifier, to keep native ESM valid in dev.\n */\nexport function collectMockExportNamesBySource(\n code: string,\n): Map<string, Array<string>> {\n const ast = parseAst({ code })\n\n const namesBySource = new Map<string, Set<string>>()\n const add = (source: string, name: string) => {\n if (name === 'default' || name.length === 0) return\n getOrCreate(namesBySource, source, () => new Set<string>()).add(name)\n }\n\n for (const node of ast.program.body) {\n if (t.isImportDeclaration(node)) {\n if (node.importKind === 'type') continue\n const source = node.source.value\n for (const s of node.specifiers) {\n if (!t.isImportSpecifier(s)) continue\n if (s.importKind === 'type') continue\n const importedName = t.isIdentifier(s.imported)\n ? s.imported.name\n : s.imported.value\n // `import { default as x } from 'm'` only requires a default export.\n if (importedName === 'default') continue\n add(source, importedName)\n }\n }\n\n if (t.isExportNamedDeclaration(node) && node.source?.value) {\n if (node.exportKind === 'type') continue\n const source = node.source.value\n for (const s of node.specifiers) {\n if (!t.isExportSpecifier(s)) continue\n if (s.exportKind === 'type') continue\n add(source, s.local.name)\n }\n }\n }\n\n const out = new Map<string, Array<string>>()\n for (const [source, set] of namesBySource) {\n out.set(source, Array.from(set).sort())\n }\n return out\n}\n\n/**\n * Rewrite static imports/re-exports from denied sources using Babel AST transforms.\n *\n * Transforms:\n * import { a as b, c } from 'denied'\n * Into:\n * import __tss_deny_0 from 'tanstack-start-import-protection:mock'\n * const b = __tss_deny_0.a\n * const c = __tss_deny_0.c\n *\n * Also handles:\n * import def from 'denied' -> import def from mock\n * import * as ns from 'denied' -> import ns from mock\n * export { x } from 'denied' -> export const x = mock.x\n * export * from 'denied' -> removed\n * export { x as y } from 'denied' -> export const y = mock.x\n */\nexport function rewriteDeniedImports(\n code: string,\n id: string,\n deniedSources: Set<string>,\n getMockModuleId: (source: string) => string = () => MOCK_MODULE_ID,\n): { code: string; map?: object | null } | undefined {\n const ast = parseAst({ code })\n let modified = false\n let mockCounter = 0\n\n // Walk program body in reverse so splice indices stay valid\n for (let i = ast.program.body.length - 1; i >= 0; i--) {\n const node = ast.program.body[i]!\n\n if (t.isImportDeclaration(node)) {\n if (node.importKind === 'type') continue\n if (!deniedSources.has(node.source.value)) continue\n\n const mockVar = `__tss_deny_${mockCounter++}`\n const replacements: Array<t.Statement> = []\n\n replacements.push(\n t.importDeclaration(\n [t.importDefaultSpecifier(t.identifier(mockVar))],\n t.stringLiteral(getMockModuleId(node.source.value)),\n ),\n )\n\n for (const specifier of node.specifiers) {\n if (\n t.isImportDefaultSpecifier(specifier) ||\n t.isImportNamespaceSpecifier(specifier)\n ) {\n replacements.push(\n t.variableDeclaration('const', [\n t.variableDeclarator(\n t.identifier(specifier.local.name),\n t.identifier(mockVar),\n ),\n ]),\n )\n } else if (t.isImportSpecifier(specifier)) {\n if (specifier.importKind === 'type') continue\n const importedName = t.isIdentifier(specifier.imported)\n ? specifier.imported.name\n : specifier.imported.value\n replacements.push(\n t.variableDeclaration('const', [\n t.variableDeclarator(\n t.identifier(specifier.local.name),\n t.memberExpression(\n t.identifier(mockVar),\n t.identifier(importedName),\n ),\n ),\n ]),\n )\n }\n }\n\n ast.program.body.splice(i, 1, ...replacements)\n modified = true\n continue\n }\n\n if (t.isExportNamedDeclaration(node) && node.source) {\n if (node.exportKind === 'type') continue\n if (!deniedSources.has(node.source.value)) continue\n\n const mockVar = `__tss_deny_${mockCounter++}`\n const replacements: Array<t.Statement> = []\n\n replacements.push(\n t.importDeclaration(\n [t.importDefaultSpecifier(t.identifier(mockVar))],\n t.stringLiteral(getMockModuleId(node.source.value)),\n ),\n )\n const exportSpecifiers: Array<{\n localName: string\n exportedName: string\n }> = []\n for (const specifier of node.specifiers) {\n if (t.isExportSpecifier(specifier)) {\n if (specifier.exportKind === 'type') continue\n const localName = specifier.local.name\n const exportedName = t.isIdentifier(specifier.exported)\n ? specifier.exported.name\n : specifier.exported.value\n\n const internalVar = `__tss_reexport_${localName}`\n replacements.push(\n t.variableDeclaration('const', [\n t.variableDeclarator(\n t.identifier(internalVar),\n t.memberExpression(\n t.identifier(mockVar),\n t.identifier(localName),\n ),\n ),\n ]),\n )\n exportSpecifiers.push({ localName: internalVar, exportedName })\n }\n }\n\n if (exportSpecifiers.length > 0) {\n replacements.push(\n t.exportNamedDeclaration(\n null,\n exportSpecifiers.map((s) =>\n t.exportSpecifier(\n t.identifier(s.localName),\n t.identifier(s.exportedName),\n ),\n ),\n ),\n )\n }\n\n ast.program.body.splice(i, 1, ...replacements)\n modified = true\n continue\n }\n\n if (t.isExportAllDeclaration(node)) {\n if (node.exportKind === 'type') continue\n if (!deniedSources.has(node.source.value)) continue\n\n ast.program.body.splice(i, 1)\n modified = true\n continue\n }\n }\n\n if (!modified) return undefined\n\n const result = generateFromAst(ast, {\n sourceMaps: true,\n sourceFileName: id,\n filename: id,\n })\n\n return { code: result.code, map: result.map }\n}\n"],"names":[],"mappings":";;;AAMO,SAAS,kBAAkB,MAAuB;AACvD,MAAI,SAAS,aAAa,KAAK,WAAW,EAAG,QAAO;AACpD,QAAM,QAAQ,KAAK,WAAW,CAAC;AAE/B,MACE,EACG,SAAS,MAAM,SAAS,MACxB,SAAS,MAAM,SAAS,OACzB,UAAU,MACV,UAAU;AAGZ,WAAO;AACT,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,KAAK,KAAK,WAAW,CAAC;AAE5B,QACE,EACG,MAAM,MAAM,MAAM,MAClB,MAAM,MAAM,MAAM,OAClB,MAAM,MAAM,MAAM,MACnB,OAAO,MACP,OAAO;AAGT,aAAO;AAAA,EACX;AACA,SAAO;AACT;AAMO,SAAS,+BACd,MAC4B;AAC5B,QAAM,MAAM,SAAS,EAAE,MAAM;AAE7B,QAAM,oCAAoB,IAAA;AAC1B,QAAM,MAAM,CAAC,QAAgB,SAAiB;AAC5C,QAAI,SAAS,aAAa,KAAK,WAAW,EAAG;AAC7C,gBAAY,eAAe,QAAQ,MAAM,oBAAI,KAAa,EAAE,IAAI,IAAI;AAAA,EACtE;AAEA,aAAW,QAAQ,IAAI,QAAQ,MAAM;AACnC,QAAI,EAAE,oBAAoB,IAAI,GAAG;AAC/B,UAAI,KAAK,eAAe,OAAQ;AAChC,YAAM,SAAS,KAAK,OAAO;AAC3B,iBAAW,KAAK,KAAK,YAAY;AAC/B,YAAI,CAAC,EAAE,kBAAkB,CAAC,EAAG;AAC7B,YAAI,EAAE,eAAe,OAAQ;AAC7B,cAAM,eAAe,EAAE,aAAa,EAAE,QAAQ,IAC1C,EAAE,SAAS,OACX,EAAE,SAAS;AAEf,YAAI,iBAAiB,UAAW;AAChC,YAAI,QAAQ,YAAY;AAAA,MAC1B;AAAA,IACF;AAEA,QAAI,EAAE,yBAAyB,IAAI,KAAK,KAAK,QAAQ,OAAO;AAC1D,UAAI,KAAK,eAAe,OAAQ;AAChC,YAAM,SAAS,KAAK,OAAO;AAC3B,iBAAW,KAAK,KAAK,YAAY;AAC/B,YAAI,CAAC,EAAE,kBAAkB,CAAC,EAAG;AAC7B,YAAI,EAAE,eAAe,OAAQ;AAC7B,YAAI,QAAQ,EAAE,MAAM,IAAI;AAAA,MAC1B;AAAA,IACF;AAAA,EACF;AAEA,QAAM,0BAAU,IAAA;AAChB,aAAW,CAAC,QAAQ,GAAG,KAAK,eAAe;AACzC,QAAI,IAAI,QAAQ,MAAM,KAAK,GAAG,EAAE,MAAM;AAAA,EACxC;AACA,SAAO;AACT;"}
@@ -1,9 +1,6 @@
1
1
  import { Loc } from './trace.js';
2
2
  /**
3
3
  * Minimal source-map shape used throughout the import-protection plugin.
4
- *
5
- * Structurally compatible with both Rollup's `SourceMap` (version: number)
6
- * and the `source-map` package's `RawSourceMap` (version: string).
7
4
  */
8
5
  export interface SourceMapLike {
9
6
  file?: string;
@@ -14,17 +11,6 @@ export interface SourceMapLike {
14
11
  sourcesContent?: Array<string | null>;
15
12
  mappings: string;
16
13
  }
17
- /**
18
- * A cached transform result for a single module.
19
- *
20
- * - `code` – fully-transformed source (after all plugins).
21
- * - `map` – composed sourcemap (chains back to the original file).
22
- * - `originalCode` – the untransformed source, extracted from the
23
- * sourcemap's `sourcesContent[0]` during the transform
24
- * hook. Used by {@link buildCodeSnippet} so we never
25
- * have to re-derive it via a flaky `sourceContentFor`
26
- * lookup at display time.
27
- */
28
14
  export interface TransformResult {
29
15
  code: string;
30
16
  map: SourceMapLike | undefined;
@@ -35,21 +21,8 @@ export interface TransformResult {
35
21
  /**
36
22
  * Provides the transformed code and composed sourcemap for a module.
37
23
  *
38
- * During `resolveId`, Vite's `this.load()` does NOT return code/map in dev
39
- * mode (the ModuleInfo proxy throws on `.code` access). Even in build mode,
40
- * Rollup's `ModuleInfo` has `.code` but not `.map`.
41
- *
42
- * Instead, we populate this cache from a late-running transform hook that
43
- * stores `{ code, map, originalCode }` for every module as it passes through
44
- * the pipeline. By the time `resolveId` fires for an import, the importer
45
- * has already been fully transformed, so the cache always has the data we
46
- * need.
47
- *
48
- * The `id` parameter is the **raw** module ID (may include Vite query
49
- * parameters like `?tsr-split=component`). Implementations should look up
50
- * with the full ID first, then fall back to the query-stripped path so that
51
- * virtual-module variants are resolved correctly without losing the base-file
52
- * fallback.
24
+ * Populated from a late-running transform hook. By the time `resolveId`
25
+ * fires for an import, the importer has already been fully transformed.
53
26
  */
54
27
  export interface TransformResultProvider {
55
28
  getTransformResult: (id: string) => TransformResult | undefined;
@@ -59,37 +32,40 @@ export type LineIndex = {
59
32
  };
60
33
  export declare function buildLineIndex(code: string): LineIndex;
61
34
  /**
62
- * Pick the most-likely original source text for `importerFile`.
63
- *
64
- * Sourcemaps can contain multiple sources (composed maps), so `sourcesContent[0]`
65
- * is not guaranteed to represent the importer.
35
+ * Pick the most-likely original source text for `importerFile` from
36
+ * a sourcemap that may contain multiple sources.
66
37
  */
67
38
  export declare function pickOriginalCodeFromSourcesContent(map: SourceMapLike | undefined, importerFile: string, root: string): string | undefined;
68
- export declare function mapGeneratedToOriginal(map: SourceMapLike | undefined, generated: {
39
+ export type ImportLocEntry = {
40
+ file?: string;
69
41
  line: number;
70
- column0: number;
71
- }, fallbackFile: string): Promise<Loc>;
72
- export declare function findFirstImportSpecifierIndex(code: string, source: string): number;
42
+ column: number;
43
+ };
73
44
  /**
74
- * Find the location of an import statement in a transformed module.
75
- *
76
- * Looks up the module's transformed code + composed sourcemap from the
77
- * {@link TransformResultProvider}, finds the import specifier in the
78
- * transformed code, and maps back to the original source via the sourcemap.
79
- *
45
+ * Cache for import statement locations with reverse index for O(1)
46
+ * invalidation by file. Keys: `${importerFile}::${source}`.
47
+ */
48
+ export declare class ImportLocCache {
49
+ private cache;
50
+ private reverseIndex;
51
+ has(key: string): boolean;
52
+ get(key: string): ImportLocEntry | null | undefined;
53
+ set(key: string, value: ImportLocEntry | null): void;
54
+ clear(): void;
55
+ /** Remove all cache entries where the importer matches `file`. */
56
+ deleteByFile(file: string): void;
57
+ }
58
+ export declare function clearImportPatternCache(): void;
59
+ /**
60
+ * Find the location of an import statement in a transformed module
61
+ * by searching the post-transform code and mapping back via sourcemap.
80
62
  * Results are cached in `importLocCache`.
81
63
  */
82
- export declare function findImportStatementLocationFromTransformed(provider: TransformResultProvider, importerId: string, source: string, importLocCache: Map<string, {
83
- file?: string;
84
- line: number;
85
- column: number;
86
- } | null>): Promise<Loc | undefined>;
64
+ export declare function findImportStatementLocationFromTransformed(provider: TransformResultProvider, importerId: string, source: string, importLocCache: ImportLocCache): Promise<Loc | undefined>;
87
65
  /**
88
66
  * Find the first post-compile usage location for a denied import specifier.
89
- *
90
- * Best-effort: looks up the module's transformed output from the
91
- * {@link TransformResultProvider}, finds the first non-import usage of
92
- * an imported binding, and maps back to original source via sourcemap.
67
+ * Best-effort: searches transformed code for non-import uses of imported
68
+ * bindings and maps back to original source via sourcemap.
93
69
  */
94
70
  export declare function findPostCompileUsageLocation(provider: TransformResultProvider, importerId: string, source: string, findPostCompileUsagePos: (code: string, source: string) => {
95
71
  line: number;
@@ -104,11 +80,7 @@ export declare function addTraceImportLocations(provider: TransformResultProvide
104
80
  specifier?: string;
105
81
  line?: number;
106
82
  column?: number;
107
- }>, importLocCache: Map<string, {
108
- file?: string;
109
- line: number;
110
- column: number;
111
- } | null>): Promise<void>;
83
+ }>, importLocCache: ImportLocCache): Promise<void>;
112
84
  export interface CodeSnippet {
113
85
  /** Source lines with line numbers, e.g. `[" 6 | import { getSecret } from './secret.server'", ...]` */
114
86
  lines: Array<string>;
@@ -118,15 +90,9 @@ export interface CodeSnippet {
118
90
  location: string;
119
91
  }
120
92
  /**
121
- * Build a vitest-style code snippet showing the lines surrounding a location.
122
- *
123
- * Uses the `originalCode` stored in the transform result cache (extracted from
124
- * `sourcesContent[0]` of the composed sourcemap at transform time). This is
125
- * reliable regardless of how the sourcemap names its sources.
126
- *
127
- * Falls back to the transformed code only when `originalCode` is unavailable
128
- * (e.g. a virtual module with no sourcemap).
93
+ * Build a vitest-style code snippet showing lines surrounding a location.
129
94
  *
130
- * @param contextLines Number of lines to show above/below the target line (default 2).
95
+ * Prefers `originalCode` from the sourcemap's sourcesContent; falls back
96
+ * to transformed code when unavailable.
131
97
  */
132
98
  export declare function buildCodeSnippet(provider: TransformResultProvider, moduleId: string, loc: Loc, contextLines?: number): CodeSnippet | undefined;
@@ -1,6 +1,6 @@
1
1
  import { SourceMapConsumer } from "source-map";
2
2
  import * as path from "pathe";
3
- import { normalizeFilePath } from "./utils.js";
3
+ import { normalizeFilePath, getOrCreate, escapeRegExp } from "./utils.js";
4
4
  function buildLineIndex(code) {
5
5
  const offsets = [0];
6
6
  for (let i = 0; i < code.length; i++) {
@@ -21,36 +21,21 @@ function upperBound(values, x) {
21
21
  return lo;
22
22
  }
23
23
  function indexToLineColWithIndex(lineIndex, idx) {
24
- let line = 1;
25
24
  const offsets = lineIndex.offsets;
26
25
  const ub = upperBound(offsets, idx);
27
26
  const lineIdx = Math.max(0, ub - 1);
28
- line = lineIdx + 1;
27
+ const line = lineIdx + 1;
29
28
  const lineStart = offsets[lineIdx] ?? 0;
30
29
  return { line, column0: Math.max(0, idx - lineStart) };
31
30
  }
32
- function suffixSegmentScore(a, b) {
33
- const aSeg = a.split("/").filter(Boolean);
34
- const bSeg = b.split("/").filter(Boolean);
35
- let score = 0;
36
- for (let i = aSeg.length - 1, j = bSeg.length - 1; i >= 0 && j >= 0; i--, j--) {
37
- if (aSeg[i] !== bSeg[j]) break;
38
- score++;
39
- }
40
- return score;
41
- }
42
- function normalizeSourceCandidate(source, root, sourceRoot) {
43
- if (!source) return "";
44
- if (path.isAbsolute(source)) return normalizeFilePath(source);
45
- const base = sourceRoot ? path.resolve(root, sourceRoot) : root;
46
- return normalizeFilePath(path.resolve(base, source));
47
- }
48
31
  function pickOriginalCodeFromSourcesContent(map, importerFile, root) {
49
32
  if (!map?.sourcesContent || map.sources.length === 0) {
50
33
  return void 0;
51
34
  }
52
35
  const file = normalizeFilePath(importerFile);
53
36
  const sourceRoot = map.sourceRoot;
37
+ const fileSeg = file.split("/").filter(Boolean);
38
+ const resolveBase = sourceRoot ? path.resolve(root, sourceRoot) : root;
54
39
  let bestIdx = -1;
55
40
  let bestScore = -1;
56
41
  for (let i = 0; i < map.sources.length; i++) {
@@ -61,13 +46,22 @@ function pickOriginalCodeFromSourcesContent(map, importerFile, root) {
61
46
  if (normalizedSrc === file) {
62
47
  return content;
63
48
  }
64
- const resolved = normalizeSourceCandidate(src, root, sourceRoot);
49
+ let resolved;
50
+ if (!src) {
51
+ resolved = "";
52
+ } else if (path.isAbsolute(src)) {
53
+ resolved = normalizeFilePath(src);
54
+ } else {
55
+ resolved = normalizeFilePath(path.resolve(resolveBase, src));
56
+ }
65
57
  if (resolved === file) {
66
58
  return content;
67
59
  }
60
+ const normalizedSrcSeg = normalizedSrc.split("/").filter(Boolean);
61
+ const resolvedSeg = resolved !== normalizedSrc ? resolved.split("/").filter(Boolean) : normalizedSrcSeg;
68
62
  const score = Math.max(
69
- suffixSegmentScore(normalizedSrc, file),
70
- suffixSegmentScore(resolved, file)
63
+ segmentSuffixScore(normalizedSrcSeg, fileSeg),
64
+ segmentSuffixScore(resolvedSeg, fileSeg)
71
65
  );
72
66
  if (score > bestScore) {
73
67
  bestScore = score;
@@ -75,11 +69,17 @@ function pickOriginalCodeFromSourcesContent(map, importerFile, root) {
75
69
  }
76
70
  }
77
71
  if (bestIdx !== -1 && bestScore >= 1) {
78
- const best = map.sourcesContent[bestIdx];
79
- return typeof best === "string" ? best : void 0;
72
+ return map.sourcesContent[bestIdx] ?? void 0;
80
73
  }
81
- const fallback = map.sourcesContent[0];
82
- return typeof fallback === "string" ? fallback : void 0;
74
+ return map.sourcesContent[0] ?? void 0;
75
+ }
76
+ function segmentSuffixScore(aSeg, bSeg) {
77
+ let score = 0;
78
+ for (let i = aSeg.length - 1, j = bSeg.length - 1; i >= 0 && j >= 0; i--, j--) {
79
+ if (aSeg[i] !== bSeg[j]) break;
80
+ score++;
81
+ }
82
+ return score;
83
83
  }
84
84
  async function mapGeneratedToOriginal(map, generated, fallbackFile) {
85
85
  const fallback = {
@@ -109,12 +109,20 @@ async function mapGeneratedToOriginal(map, generated, fallbackFile) {
109
109
  return fallback;
110
110
  }
111
111
  const consumerCache = /* @__PURE__ */ new WeakMap();
112
+ function toRawSourceMap(map) {
113
+ return {
114
+ ...map,
115
+ file: map.file ?? "",
116
+ version: Number(map.version),
117
+ sourcesContent: map.sourcesContent?.map((s) => s ?? "") ?? []
118
+ };
119
+ }
112
120
  async function getSourceMapConsumer(map) {
113
121
  const cached = consumerCache.get(map);
114
122
  if (cached) return cached;
115
123
  const promise = (async () => {
116
124
  try {
117
- return await new SourceMapConsumer(map);
125
+ return await new SourceMapConsumer(toRawSourceMap(map));
118
126
  } catch {
119
127
  return null;
120
128
  }
@@ -122,16 +130,50 @@ async function getSourceMapConsumer(map) {
122
130
  consumerCache.set(map, promise);
123
131
  return promise;
124
132
  }
133
+ class ImportLocCache {
134
+ cache = /* @__PURE__ */ new Map();
135
+ reverseIndex = /* @__PURE__ */ new Map();
136
+ has(key) {
137
+ return this.cache.has(key);
138
+ }
139
+ get(key) {
140
+ return this.cache.get(key);
141
+ }
142
+ set(key, value) {
143
+ this.cache.set(key, value);
144
+ const file = key.slice(0, key.indexOf("::"));
145
+ getOrCreate(this.reverseIndex, file, () => /* @__PURE__ */ new Set()).add(key);
146
+ }
147
+ clear() {
148
+ this.cache.clear();
149
+ this.reverseIndex.clear();
150
+ }
151
+ /** Remove all cache entries where the importer matches `file`. */
152
+ deleteByFile(file) {
153
+ const keys = this.reverseIndex.get(file);
154
+ if (keys) {
155
+ for (const key of keys) {
156
+ this.cache.delete(key);
157
+ }
158
+ this.reverseIndex.delete(file);
159
+ }
160
+ }
161
+ }
162
+ const importPatternCache = /* @__PURE__ */ new Map();
163
+ function clearImportPatternCache() {
164
+ importPatternCache.clear();
165
+ }
125
166
  function findFirstImportSpecifierIndex(code, source) {
126
- const escaped = source.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
127
- const patterns = [
128
- // import 'x'
129
- new RegExp(`\\bimport\\s+(['"])${escaped}\\1`),
130
- // import ... from 'x' / export ... from 'x'
131
- new RegExp(`\\bfrom\\s+(['"])${escaped}\\1`),
132
- // import('x')
133
- new RegExp(`\\bimport\\s*\\(\\s*(['"])${escaped}\\1\\s*\\)`)
134
- ];
167
+ let patterns = importPatternCache.get(source);
168
+ if (!patterns) {
169
+ const escaped = escapeRegExp(source);
170
+ patterns = [
171
+ new RegExp(`\\bimport\\s+(['"])${escaped}\\1`),
172
+ new RegExp(`\\bfrom\\s+(['"])${escaped}\\1`),
173
+ new RegExp(`\\bimport\\s*\\(\\s*(['"])${escaped}\\1\\s*\\)`)
174
+ ];
175
+ importPatternCache.set(source, patterns);
176
+ }
135
177
  let best = -1;
136
178
  for (const re of patterns) {
137
179
  const m = re.exec(code);
@@ -146,7 +188,7 @@ async function findImportStatementLocationFromTransformed(provider, importerId,
146
188
  const importerFile = normalizeFilePath(importerId);
147
189
  const cacheKey = `${importerFile}::${source}`;
148
190
  if (importLocCache.has(cacheKey)) {
149
- return importLocCache.get(cacheKey) || void 0;
191
+ return importLocCache.get(cacheKey) ?? void 0;
150
192
  }
151
193
  try {
152
194
  const res = provider.getTransformResult(importerId);
@@ -155,10 +197,6 @@ async function findImportStatementLocationFromTransformed(provider, importerId,
155
197
  return void 0;
156
198
  }
157
199
  const { code, map } = res;
158
- if (typeof code !== "string") {
159
- importLocCache.set(cacheKey, null);
160
- return void 0;
161
- }
162
200
  const lineIndex = res.lineIndex ?? buildLineIndex(code);
163
201
  const idx = findFirstImportSpecifierIndex(code, source);
164
202
  if (idx === -1) {
@@ -180,7 +218,6 @@ async function findPostCompileUsageLocation(provider, importerId, source, findPo
180
218
  const res = provider.getTransformResult(importerId);
181
219
  if (!res) return void 0;
182
220
  const { code, map } = res;
183
- if (typeof code !== "string") return void 0;
184
221
  if (!res.lineIndex) {
185
222
  res.lineIndex = buildLineIndex(code);
186
223
  }
@@ -212,23 +249,59 @@ function buildCodeSnippet(provider, moduleId, loc, contextLines = 2) {
212
249
  const res = provider.getTransformResult(moduleId);
213
250
  if (!res) return void 0;
214
251
  const { code: transformedCode, originalCode } = res;
215
- if (typeof transformedCode !== "string") return void 0;
216
252
  const sourceCode = originalCode ?? transformedCode;
217
- const allLines = sourceCode.split(/\r?\n/);
218
253
  const targetLine = loc.line;
219
254
  const targetCol = loc.column;
220
- if (targetLine < 1 || targetLine > allLines.length) return void 0;
221
- const startLine = Math.max(1, targetLine - contextLines);
222
- const endLine = Math.min(allLines.length, targetLine + contextLines);
223
- const gutterWidth = String(endLine).length;
255
+ if (targetLine < 1) return void 0;
256
+ const wantStart = Math.max(1, targetLine - contextLines);
257
+ const wantEnd = targetLine + contextLines;
258
+ let lineNum = 1;
259
+ let pos = 0;
260
+ while (lineNum < wantStart && pos < sourceCode.length) {
261
+ const ch = sourceCode.charCodeAt(pos);
262
+ if (ch === 10) {
263
+ lineNum++;
264
+ } else if (ch === 13) {
265
+ lineNum++;
266
+ if (pos + 1 < sourceCode.length && sourceCode.charCodeAt(pos + 1) === 10)
267
+ pos++;
268
+ }
269
+ pos++;
270
+ }
271
+ if (lineNum < wantStart) return void 0;
272
+ const lines = [];
273
+ let curLine = wantStart;
274
+ while (curLine <= wantEnd && pos <= sourceCode.length) {
275
+ let eol = pos;
276
+ while (eol < sourceCode.length) {
277
+ const ch = sourceCode.charCodeAt(eol);
278
+ if (ch === 10 || ch === 13) break;
279
+ eol++;
280
+ }
281
+ lines.push(sourceCode.slice(pos, eol));
282
+ curLine++;
283
+ if (eol < sourceCode.length) {
284
+ if (sourceCode.charCodeAt(eol) === 13 && eol + 1 < sourceCode.length && sourceCode.charCodeAt(eol + 1) === 10) {
285
+ pos = eol + 2;
286
+ } else {
287
+ pos = eol + 1;
288
+ }
289
+ } else {
290
+ pos = eol + 1;
291
+ }
292
+ }
293
+ if (targetLine > wantStart + lines.length - 1) return void 0;
294
+ const actualEnd = wantStart + lines.length - 1;
295
+ const gutterWidth = String(actualEnd).length;
224
296
  const sourceFile = loc.file ?? importerFile;
225
297
  const snippetLines = [];
226
- for (let i = startLine; i <= endLine; i++) {
227
- const lineContent = allLines[i - 1] ?? "";
228
- const lineNum = String(i).padStart(gutterWidth, " ");
229
- const marker = i === targetLine ? ">" : " ";
230
- snippetLines.push(` ${marker} ${lineNum} | ${lineContent}`);
231
- if (i === targetLine && targetCol > 0) {
298
+ for (let i = 0; i < lines.length; i++) {
299
+ const ln = wantStart + i;
300
+ const lineContent = lines[i];
301
+ const lineNumStr = String(ln).padStart(gutterWidth, " ");
302
+ const marker = ln === targetLine ? ">" : " ";
303
+ snippetLines.push(` ${marker} ${lineNumStr} | ${lineContent}`);
304
+ if (ln === targetLine && targetCol > 0) {
232
305
  const padding = " ".repeat(targetCol - 1);
233
306
  snippetLines.push(` ${" ".repeat(gutterWidth)} | ${padding}^`);
234
307
  }
@@ -243,13 +316,13 @@ function buildCodeSnippet(provider, moduleId, loc, contextLines = 2) {
243
316
  }
244
317
  }
245
318
  export {
319
+ ImportLocCache,
246
320
  addTraceImportLocations,
247
321
  buildCodeSnippet,
248
322
  buildLineIndex,
249
- findFirstImportSpecifierIndex,
323
+ clearImportPatternCache,
250
324
  findImportStatementLocationFromTransformed,
251
325
  findPostCompileUsageLocation,
252
- mapGeneratedToOriginal,
253
326
  pickOriginalCodeFromSourcesContent
254
327
  };
255
328
  //# sourceMappingURL=sourceLocation.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sourceLocation.js","sources":["../../../src/import-protection-plugin/sourceLocation.ts"],"sourcesContent":["import { SourceMapConsumer } from 'source-map'\nimport * as path from 'pathe'\n\nimport { normalizeFilePath } from './utils'\nimport type { Loc } from './trace'\nimport type { RawSourceMap } from 'source-map'\n\n// ---------------------------------------------------------------------------\n// Source-map type compatible with both Rollup's SourceMap and source-map's\n// RawSourceMap. We define our own structural type so that the value returned\n// by `getCombinedSourcemap()` (version: number) flows seamlessly into\n// `SourceMapConsumer` (version: string) without requiring a cast.\n// ---------------------------------------------------------------------------\n\n/**\n * Minimal source-map shape used throughout the import-protection plugin.\n *\n * Structurally compatible with both Rollup's `SourceMap` (version: number)\n * and the `source-map` package's `RawSourceMap` (version: string).\n */\nexport interface SourceMapLike {\n file?: string\n sourceRoot?: string\n version: number | string\n sources: Array<string>\n names: Array<string>\n sourcesContent?: Array<string | null>\n mappings: string\n}\n\n// ---------------------------------------------------------------------------\n// Transform result provider (replaces ctx.load() which doesn't work in dev)\n// ---------------------------------------------------------------------------\n\n/**\n * A cached transform result for a single module.\n *\n * - `code` – fully-transformed source (after all plugins).\n * - `map` – composed sourcemap (chains back to the original file).\n * - `originalCode` – the untransformed source, extracted from the\n * sourcemap's `sourcesContent[0]` during the transform\n * hook. Used by {@link buildCodeSnippet} so we never\n * have to re-derive it via a flaky `sourceContentFor`\n * lookup at display time.\n */\nexport interface TransformResult {\n code: string\n map: SourceMapLike | undefined\n originalCode: string | undefined\n /** Precomputed line index for `code` (index → line/col). */\n lineIndex?: LineIndex\n}\n\n/**\n * Provides the transformed code and composed sourcemap for a module.\n *\n * During `resolveId`, Vite's `this.load()` does NOT return code/map in dev\n * mode (the ModuleInfo proxy throws on `.code` access). Even in build mode,\n * Rollup's `ModuleInfo` has `.code` but not `.map`.\n *\n * Instead, we populate this cache from a late-running transform hook that\n * stores `{ code, map, originalCode }` for every module as it passes through\n * the pipeline. By the time `resolveId` fires for an import, the importer\n * has already been fully transformed, so the cache always has the data we\n * need.\n *\n * The `id` parameter is the **raw** module ID (may include Vite query\n * parameters like `?tsr-split=component`). Implementations should look up\n * with the full ID first, then fall back to the query-stripped path so that\n * virtual-module variants are resolved correctly without losing the base-file\n * fallback.\n */\nexport interface TransformResultProvider {\n getTransformResult: (id: string) => TransformResult | undefined\n}\n\n// ---------------------------------------------------------------------------\n// Index → line/column conversion\n// ---------------------------------------------------------------------------\n\nexport type LineIndex = {\n offsets: Array<number>\n}\n\nexport function buildLineIndex(code: string): LineIndex {\n const offsets: Array<number> = [0]\n for (let i = 0; i < code.length; i++) {\n if (code.charCodeAt(i) === 10) {\n offsets.push(i + 1)\n }\n }\n return { offsets }\n}\n\nfunction upperBound(values: Array<number>, x: number): number {\n let lo = 0\n let hi = values.length\n while (lo < hi) {\n const mid = (lo + hi) >> 1\n if (values[mid]! <= x) lo = mid + 1\n else hi = mid\n }\n return lo\n}\n\nfunction indexToLineColWithIndex(\n lineIndex: LineIndex,\n idx: number,\n): { line: number; column0: number } {\n let line = 1\n\n const offsets = lineIndex.offsets\n const ub = upperBound(offsets, idx)\n const lineIdx = Math.max(0, ub - 1)\n line = lineIdx + 1\n\n const lineStart = offsets[lineIdx] ?? 0\n return { line, column0: Math.max(0, idx - lineStart) }\n}\n\n// ---------------------------------------------------------------------------\n// Pick the best original source from sourcesContent\n// ---------------------------------------------------------------------------\n\nfunction suffixSegmentScore(a: string, b: string): number {\n const aSeg = a.split('/').filter(Boolean)\n const bSeg = b.split('/').filter(Boolean)\n let score = 0\n for (\n let i = aSeg.length - 1, j = bSeg.length - 1;\n i >= 0 && j >= 0;\n i--, j--\n ) {\n if (aSeg[i] !== bSeg[j]) break\n score++\n }\n return score\n}\n\nfunction normalizeSourceCandidate(\n source: string,\n root: string,\n sourceRoot: string | undefined,\n): string {\n // Prefer resolving relative source paths against root/sourceRoot when present.\n if (!source) return ''\n if (path.isAbsolute(source)) return normalizeFilePath(source)\n const base = sourceRoot ? path.resolve(root, sourceRoot) : root\n return normalizeFilePath(path.resolve(base, source))\n}\n\n/**\n * Pick the most-likely original source text for `importerFile`.\n *\n * Sourcemaps can contain multiple sources (composed maps), so `sourcesContent[0]`\n * is not guaranteed to represent the importer.\n */\nexport function pickOriginalCodeFromSourcesContent(\n map: SourceMapLike | undefined,\n importerFile: string,\n root: string,\n): string | undefined {\n if (!map?.sourcesContent || map.sources.length === 0) {\n return undefined\n }\n\n const file = normalizeFilePath(importerFile)\n const sourceRoot = map.sourceRoot\n\n let bestIdx = -1\n let bestScore = -1\n\n for (let i = 0; i < map.sources.length; i++) {\n const content = map.sourcesContent[i]\n if (typeof content !== 'string') continue\n\n const src = map.sources[i] ?? ''\n\n // Exact match via raw normalized source.\n const normalizedSrc = normalizeFilePath(src)\n if (normalizedSrc === file) {\n return content\n }\n\n // Exact match via resolved absolute candidate.\n const resolved = normalizeSourceCandidate(src, root, sourceRoot)\n if (resolved === file) {\n return content\n }\n\n const score = Math.max(\n suffixSegmentScore(normalizedSrc, file),\n suffixSegmentScore(resolved, file),\n )\n\n if (score > bestScore) {\n bestScore = score\n bestIdx = i\n }\n }\n\n // Require at least a basename match; otherwise fall back to index 0.\n if (bestIdx !== -1 && bestScore >= 1) {\n const best = map.sourcesContent[bestIdx]\n return typeof best === 'string' ? best : undefined\n }\n\n const fallback = map.sourcesContent[0]\n return typeof fallback === 'string' ? fallback : undefined\n}\n\n// ---------------------------------------------------------------------------\n// Sourcemap: generated → original mapping\n// ---------------------------------------------------------------------------\n\nexport async function mapGeneratedToOriginal(\n map: SourceMapLike | undefined,\n generated: { line: number; column0: number },\n fallbackFile: string,\n): Promise<Loc> {\n const fallback: Loc = {\n file: fallbackFile,\n line: generated.line,\n column: generated.column0 + 1,\n }\n\n if (!map) {\n return fallback\n }\n\n const consumer = await getSourceMapConsumer(map)\n if (!consumer) return fallback\n\n try {\n const orig = consumer.originalPositionFor({\n line: generated.line,\n column: generated.column0,\n })\n if (orig.line != null && orig.column != null) {\n return {\n file: orig.source ? normalizeFilePath(orig.source) : fallbackFile,\n line: orig.line,\n column: orig.column + 1,\n }\n }\n } catch {\n // Invalid or malformed sourcemap — fall through to fallback.\n }\n\n return fallback\n}\n\n// Cache SourceMapConsumer per sourcemap object.\nconst consumerCache = new WeakMap<object, Promise<SourceMapConsumer | null>>()\n\nasync function getSourceMapConsumer(\n map: SourceMapLike,\n): Promise<SourceMapConsumer | null> {\n // WeakMap requires an object key; SourceMapLike should be an object in all\n // real cases, but guard anyway.\n // (TypeScript already guarantees `map` is an object here.)\n\n const cached = consumerCache.get(map)\n if (cached) return cached\n\n const promise = (async () => {\n try {\n return await new SourceMapConsumer(map as unknown as RawSourceMap)\n } catch {\n return null\n }\n })()\n\n consumerCache.set(map, promise)\n return promise\n}\n\n// ---------------------------------------------------------------------------\n// Import specifier search (regex-based, no AST needed)\n// ---------------------------------------------------------------------------\n\nexport function findFirstImportSpecifierIndex(\n code: string,\n source: string,\n): number {\n const escaped = source.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n\n const patterns: Array<RegExp> = [\n // import 'x'\n new RegExp(`\\\\bimport\\\\s+(['\"])${escaped}\\\\1`),\n // import ... from 'x' / export ... from 'x'\n new RegExp(`\\\\bfrom\\\\s+(['\"])${escaped}\\\\1`),\n // import('x')\n new RegExp(`\\\\bimport\\\\s*\\\\(\\\\s*(['\"])${escaped}\\\\1\\\\s*\\\\)`),\n ]\n\n let best = -1\n for (const re of patterns) {\n const m = re.exec(code)\n if (!m) continue\n const idx = m.index + m[0].indexOf(source)\n if (idx === -1) continue\n if (best === -1 || idx < best) best = idx\n }\n return best\n}\n\n// ---------------------------------------------------------------------------\n// High-level location finders (use transform result cache, no disk reads)\n// ---------------------------------------------------------------------------\n\n/**\n * Find the location of an import statement in a transformed module.\n *\n * Looks up the module's transformed code + composed sourcemap from the\n * {@link TransformResultProvider}, finds the import specifier in the\n * transformed code, and maps back to the original source via the sourcemap.\n *\n * Results are cached in `importLocCache`.\n */\nexport async function findImportStatementLocationFromTransformed(\n provider: TransformResultProvider,\n importerId: string,\n source: string,\n importLocCache: Map<\n string,\n { file?: string; line: number; column: number } | null\n >,\n): Promise<Loc | undefined> {\n const importerFile = normalizeFilePath(importerId)\n const cacheKey = `${importerFile}::${source}`\n if (importLocCache.has(cacheKey)) {\n return importLocCache.get(cacheKey) || undefined\n }\n\n try {\n // Pass the raw importerId so the provider can look up the exact virtual\n // module variant (e.g. ?tsr-split=component) before falling back to the\n // base file path.\n const res = provider.getTransformResult(importerId)\n if (!res) {\n importLocCache.set(cacheKey, null)\n return undefined\n }\n\n const { code, map } = res\n if (typeof code !== 'string') {\n importLocCache.set(cacheKey, null)\n return undefined\n }\n\n const lineIndex = res.lineIndex ?? buildLineIndex(code)\n\n const idx = findFirstImportSpecifierIndex(code, source)\n if (idx === -1) {\n importLocCache.set(cacheKey, null)\n return undefined\n }\n\n const generated = indexToLineColWithIndex(lineIndex, idx)\n const loc = await mapGeneratedToOriginal(map, generated, importerFile)\n importLocCache.set(cacheKey, loc)\n return loc\n } catch {\n importLocCache.set(cacheKey, null)\n return undefined\n }\n}\n\n/**\n * Find the first post-compile usage location for a denied import specifier.\n *\n * Best-effort: looks up the module's transformed output from the\n * {@link TransformResultProvider}, finds the first non-import usage of\n * an imported binding, and maps back to original source via sourcemap.\n */\nexport async function findPostCompileUsageLocation(\n provider: TransformResultProvider,\n importerId: string,\n source: string,\n findPostCompileUsagePos: (\n code: string,\n source: string,\n ) => { line: number; column0: number } | undefined,\n): Promise<Loc | undefined> {\n try {\n const importerFile = normalizeFilePath(importerId)\n // Pass the raw importerId so the provider can look up the exact virtual\n // module variant (e.g. ?tsr-split=component) before falling back to the\n // base file path.\n const res = provider.getTransformResult(importerId)\n if (!res) return undefined\n const { code, map } = res\n if (typeof code !== 'string') return undefined\n\n // Ensure we have a line index ready for any downstream mapping.\n // (We don't currently need it here, but keeping it hot improves locality\n // for callers that also need import-statement mapping.)\n if (!res.lineIndex) {\n res.lineIndex = buildLineIndex(code)\n }\n\n const pos = findPostCompileUsagePos(code, source)\n if (!pos) return undefined\n\n return await mapGeneratedToOriginal(map, pos, importerFile)\n } catch {\n return undefined\n }\n}\n\n/**\n * Annotate each trace hop with the location of the import that created the\n * edge (file:line:col). Skips steps that already have a location.\n */\nexport async function addTraceImportLocations(\n provider: TransformResultProvider,\n trace: Array<{\n file: string\n specifier?: string\n line?: number\n column?: number\n }>,\n importLocCache: Map<\n string,\n { file?: string; line: number; column: number } | null\n >,\n): Promise<void> {\n for (const step of trace) {\n if (!step.specifier) continue\n if (step.line != null && step.column != null) continue\n const loc = await findImportStatementLocationFromTransformed(\n provider,\n step.file,\n step.specifier,\n importLocCache,\n )\n if (!loc) continue\n step.line = loc.line\n step.column = loc.column\n }\n}\n\n// ---------------------------------------------------------------------------\n// Code snippet extraction (vitest-style context around a location)\n// ---------------------------------------------------------------------------\n\nexport interface CodeSnippet {\n /** Source lines with line numbers, e.g. `[\" 6 | import { getSecret } from './secret.server'\", ...]` */\n lines: Array<string>\n /** The highlighted line (1-indexed original line number) */\n highlightLine: number\n /** Clickable file:line reference */\n location: string\n}\n\n/**\n * Build a vitest-style code snippet showing the lines surrounding a location.\n *\n * Uses the `originalCode` stored in the transform result cache (extracted from\n * `sourcesContent[0]` of the composed sourcemap at transform time). This is\n * reliable regardless of how the sourcemap names its sources.\n *\n * Falls back to the transformed code only when `originalCode` is unavailable\n * (e.g. a virtual module with no sourcemap).\n *\n * @param contextLines Number of lines to show above/below the target line (default 2).\n */\nexport function buildCodeSnippet(\n provider: TransformResultProvider,\n moduleId: string,\n loc: Loc,\n contextLines: number = 2,\n): CodeSnippet | undefined {\n try {\n const importerFile = normalizeFilePath(moduleId)\n // Pass the raw moduleId so the provider can look up the exact virtual\n // module variant (e.g. ?tsr-split=component) before falling back to the\n // base file path.\n const res = provider.getTransformResult(moduleId)\n if (!res) return undefined\n\n const { code: transformedCode, originalCode } = res\n if (typeof transformedCode !== 'string') return undefined\n\n // Prefer the original source that was captured at transform time from the\n // sourcemap's sourcesContent. This avoids the source-name-mismatch\n // problem that plagued the old sourceContentFor()-based lookup.\n const sourceCode = originalCode ?? transformedCode\n\n const allLines = sourceCode.split(/\\r?\\n/)\n const targetLine = loc.line // 1-indexed\n const targetCol = loc.column // 1-indexed\n\n if (targetLine < 1 || targetLine > allLines.length) return undefined\n\n const startLine = Math.max(1, targetLine - contextLines)\n const endLine = Math.min(allLines.length, targetLine + contextLines)\n const gutterWidth = String(endLine).length\n\n const sourceFile = loc.file ?? importerFile\n const snippetLines: Array<string> = []\n for (let i = startLine; i <= endLine; i++) {\n const lineContent = allLines[i - 1] ?? ''\n const lineNum = String(i).padStart(gutterWidth, ' ')\n const marker = i === targetLine ? '>' : ' '\n snippetLines.push(` ${marker} ${lineNum} | ${lineContent}`)\n\n // Add column pointer on the target line\n if (i === targetLine && targetCol > 0) {\n const padding = ' '.repeat(targetCol - 1)\n snippetLines.push(` ${' '.repeat(gutterWidth)} | ${padding}^`)\n }\n }\n\n return {\n lines: snippetLines,\n highlightLine: targetLine,\n location: `${sourceFile}:${targetLine}:${targetCol}`,\n }\n } catch {\n return undefined\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sourceLocation.js","sources":["../../../src/import-protection-plugin/sourceLocation.ts"],"sourcesContent":["import { SourceMapConsumer } from 'source-map'\nimport * as path from 'pathe'\n\nimport { escapeRegExp, getOrCreate, normalizeFilePath } from './utils'\nimport type { Loc } from './trace'\nimport type { RawSourceMap } from 'source-map'\n\n// Source-map type compatible with both Rollup's SourceMap and source-map's\n// RawSourceMap. Structural type avoids version: number vs string mismatch.\n\n/**\n * Minimal source-map shape used throughout the import-protection plugin.\n */\nexport interface SourceMapLike {\n file?: string\n sourceRoot?: string\n version: number | string\n sources: Array<string>\n names: Array<string>\n sourcesContent?: Array<string | null>\n mappings: string\n}\n\n// Transform result provider (replaces ctx.load() which doesn't work in dev)\nexport interface TransformResult {\n code: string\n map: SourceMapLike | undefined\n originalCode: string | undefined\n /** Precomputed line index for `code` (index → line/col). */\n lineIndex?: LineIndex\n}\n\n/**\n * Provides the transformed code and composed sourcemap for a module.\n *\n * Populated from a late-running transform hook. By the time `resolveId`\n * fires for an import, the importer has already been fully transformed.\n */\nexport interface TransformResultProvider {\n getTransformResult: (id: string) => TransformResult | undefined\n}\n\n// Index → line/column conversion\n\nexport type LineIndex = {\n offsets: Array<number>\n}\n\nexport function buildLineIndex(code: string): LineIndex {\n const offsets: Array<number> = [0]\n for (let i = 0; i < code.length; i++) {\n if (code.charCodeAt(i) === 10) {\n offsets.push(i + 1)\n }\n }\n return { offsets }\n}\n\nfunction upperBound(values: Array<number>, x: number): number {\n let lo = 0\n let hi = values.length\n while (lo < hi) {\n const mid = (lo + hi) >> 1\n if (values[mid]! <= x) lo = mid + 1\n else hi = mid\n }\n return lo\n}\n\nfunction indexToLineColWithIndex(\n lineIndex: LineIndex,\n idx: number,\n): { line: number; column0: number } {\n const offsets = lineIndex.offsets\n const ub = upperBound(offsets, idx)\n const lineIdx = Math.max(0, ub - 1)\n const line = lineIdx + 1\n\n const lineStart = offsets[lineIdx] ?? 0\n return { line, column0: Math.max(0, idx - lineStart) }\n}\n\n/**\n * Pick the most-likely original source text for `importerFile` from\n * a sourcemap that may contain multiple sources.\n */\nexport function pickOriginalCodeFromSourcesContent(\n map: SourceMapLike | undefined,\n importerFile: string,\n root: string,\n): string | undefined {\n if (!map?.sourcesContent || map.sources.length === 0) {\n return undefined\n }\n\n const file = normalizeFilePath(importerFile)\n const sourceRoot = map.sourceRoot\n const fileSeg = file.split('/').filter(Boolean)\n\n const resolveBase = sourceRoot ? path.resolve(root, sourceRoot) : root\n\n let bestIdx = -1\n let bestScore = -1\n\n for (let i = 0; i < map.sources.length; i++) {\n const content = map.sourcesContent[i]\n if (typeof content !== 'string') continue\n\n const src = map.sources[i] ?? ''\n\n const normalizedSrc = normalizeFilePath(src)\n if (normalizedSrc === file) {\n return content\n }\n\n let resolved: string\n if (!src) {\n resolved = ''\n } else if (path.isAbsolute(src)) {\n resolved = normalizeFilePath(src)\n } else {\n resolved = normalizeFilePath(path.resolve(resolveBase, src))\n }\n if (resolved === file) {\n return content\n }\n\n // Count matching path segments from the end.\n const normalizedSrcSeg = normalizedSrc.split('/').filter(Boolean)\n const resolvedSeg =\n resolved !== normalizedSrc\n ? resolved.split('/').filter(Boolean)\n : normalizedSrcSeg\n const score = Math.max(\n segmentSuffixScore(normalizedSrcSeg, fileSeg),\n segmentSuffixScore(resolvedSeg, fileSeg),\n )\n\n if (score > bestScore) {\n bestScore = score\n bestIdx = i\n }\n }\n\n if (bestIdx !== -1 && bestScore >= 1) {\n return map.sourcesContent[bestIdx] ?? undefined\n }\n\n return map.sourcesContent[0] ?? undefined\n}\n\n/** Count matching path segments from the end of `aSeg` against `bSeg`. */\nfunction segmentSuffixScore(aSeg: Array<string>, bSeg: Array<string>): number {\n let score = 0\n for (\n let i = aSeg.length - 1, j = bSeg.length - 1;\n i >= 0 && j >= 0;\n i--, j--\n ) {\n if (aSeg[i] !== bSeg[j]) break\n score++\n }\n return score\n}\n\nasync function mapGeneratedToOriginal(\n map: SourceMapLike | undefined,\n generated: { line: number; column0: number },\n fallbackFile: string,\n): Promise<Loc> {\n const fallback: Loc = {\n file: fallbackFile,\n line: generated.line,\n column: generated.column0 + 1,\n }\n\n if (!map) {\n return fallback\n }\n\n const consumer = await getSourceMapConsumer(map)\n if (!consumer) return fallback\n\n try {\n const orig = consumer.originalPositionFor({\n line: generated.line,\n column: generated.column0,\n })\n if (orig.line != null && orig.column != null) {\n return {\n file: orig.source ? normalizeFilePath(orig.source) : fallbackFile,\n line: orig.line,\n column: orig.column + 1,\n }\n }\n } catch {\n // Malformed sourcemap\n }\n\n return fallback\n}\n\nconst consumerCache = new WeakMap<object, Promise<SourceMapConsumer | null>>()\n\nfunction toRawSourceMap(map: SourceMapLike): RawSourceMap {\n return {\n ...map,\n file: map.file ?? '',\n version: Number(map.version),\n sourcesContent: map.sourcesContent?.map((s) => s ?? '') ?? [],\n }\n}\n\nasync function getSourceMapConsumer(\n map: SourceMapLike,\n): Promise<SourceMapConsumer | null> {\n const cached = consumerCache.get(map)\n if (cached) return cached\n\n const promise = (async () => {\n try {\n return await new SourceMapConsumer(toRawSourceMap(map))\n } catch {\n return null\n }\n })()\n\n consumerCache.set(map, promise)\n return promise\n}\n\nexport type ImportLocEntry = { file?: string; line: number; column: number }\n\n/**\n * Cache for import statement locations with reverse index for O(1)\n * invalidation by file. Keys: `${importerFile}::${source}`.\n */\nexport class ImportLocCache {\n private cache = new Map<string, ImportLocEntry | null>()\n private reverseIndex = new Map<string, Set<string>>()\n\n has(key: string): boolean {\n return this.cache.has(key)\n }\n\n get(key: string): ImportLocEntry | null | undefined {\n return this.cache.get(key)\n }\n\n set(key: string, value: ImportLocEntry | null): void {\n this.cache.set(key, value)\n const file = key.slice(0, key.indexOf('::'))\n getOrCreate(this.reverseIndex, file, () => new Set()).add(key)\n }\n\n clear(): void {\n this.cache.clear()\n this.reverseIndex.clear()\n }\n\n /** Remove all cache entries where the importer matches `file`. */\n deleteByFile(file: string): void {\n const keys = this.reverseIndex.get(file)\n if (keys) {\n for (const key of keys) {\n this.cache.delete(key)\n }\n this.reverseIndex.delete(file)\n }\n }\n}\n\n// Import specifier search (regex-based)\n\nconst importPatternCache = new Map<string, Array<RegExp>>()\n\nexport function clearImportPatternCache(): void {\n importPatternCache.clear()\n}\n\nfunction findFirstImportSpecifierIndex(code: string, source: string): number {\n let patterns = importPatternCache.get(source)\n if (!patterns) {\n const escaped = escapeRegExp(source)\n patterns = [\n new RegExp(`\\\\bimport\\\\s+(['\"])${escaped}\\\\1`),\n new RegExp(`\\\\bfrom\\\\s+(['\"])${escaped}\\\\1`),\n new RegExp(`\\\\bimport\\\\s*\\\\(\\\\s*(['\"])${escaped}\\\\1\\\\s*\\\\)`),\n ]\n importPatternCache.set(source, patterns)\n }\n\n let best = -1\n for (const re of patterns) {\n const m = re.exec(code)\n if (!m) continue\n const idx = m.index + m[0].indexOf(source)\n if (idx === -1) continue\n if (best === -1 || idx < best) best = idx\n }\n return best\n}\n\n/**\n * Find the location of an import statement in a transformed module\n * by searching the post-transform code and mapping back via sourcemap.\n * Results are cached in `importLocCache`.\n */\nexport async function findImportStatementLocationFromTransformed(\n provider: TransformResultProvider,\n importerId: string,\n source: string,\n importLocCache: ImportLocCache,\n): Promise<Loc | undefined> {\n const importerFile = normalizeFilePath(importerId)\n const cacheKey = `${importerFile}::${source}`\n if (importLocCache.has(cacheKey)) {\n return importLocCache.get(cacheKey) ?? undefined\n }\n\n try {\n const res = provider.getTransformResult(importerId)\n if (!res) {\n importLocCache.set(cacheKey, null)\n return undefined\n }\n\n const { code, map } = res\n\n const lineIndex = res.lineIndex ?? buildLineIndex(code)\n\n const idx = findFirstImportSpecifierIndex(code, source)\n if (idx === -1) {\n importLocCache.set(cacheKey, null)\n return undefined\n }\n\n const generated = indexToLineColWithIndex(lineIndex, idx)\n const loc = await mapGeneratedToOriginal(map, generated, importerFile)\n importLocCache.set(cacheKey, loc)\n return loc\n } catch {\n importLocCache.set(cacheKey, null)\n return undefined\n }\n}\n\n/**\n * Find the first post-compile usage location for a denied import specifier.\n * Best-effort: searches transformed code for non-import uses of imported\n * bindings and maps back to original source via sourcemap.\n */\nexport async function findPostCompileUsageLocation(\n provider: TransformResultProvider,\n importerId: string,\n source: string,\n findPostCompileUsagePos: (\n code: string,\n source: string,\n ) => { line: number; column0: number } | undefined,\n): Promise<Loc | undefined> {\n try {\n const importerFile = normalizeFilePath(importerId)\n const res = provider.getTransformResult(importerId)\n if (!res) return undefined\n const { code, map } = res\n\n if (!res.lineIndex) {\n res.lineIndex = buildLineIndex(code)\n }\n\n const pos = findPostCompileUsagePos(code, source)\n if (!pos) return undefined\n\n return await mapGeneratedToOriginal(map, pos, importerFile)\n } catch {\n return undefined\n }\n}\n\n/**\n * Annotate each trace hop with the location of the import that created the\n * edge (file:line:col). Skips steps that already have a location.\n */\nexport async function addTraceImportLocations(\n provider: TransformResultProvider,\n trace: Array<{\n file: string\n specifier?: string\n line?: number\n column?: number\n }>,\n importLocCache: ImportLocCache,\n): Promise<void> {\n for (const step of trace) {\n if (!step.specifier) continue\n if (step.line != null && step.column != null) continue\n const loc = await findImportStatementLocationFromTransformed(\n provider,\n step.file,\n step.specifier,\n importLocCache,\n )\n if (!loc) continue\n step.line = loc.line\n step.column = loc.column\n }\n}\n\n// Code snippet extraction (vitest-style context around a location)\n\nexport interface CodeSnippet {\n /** Source lines with line numbers, e.g. `[\" 6 | import { getSecret } from './secret.server'\", ...]` */\n lines: Array<string>\n /** The highlighted line (1-indexed original line number) */\n highlightLine: number\n /** Clickable file:line reference */\n location: string\n}\n\n/**\n * Build a vitest-style code snippet showing lines surrounding a location.\n *\n * Prefers `originalCode` from the sourcemap's sourcesContent; falls back\n * to transformed code when unavailable.\n */\nexport function buildCodeSnippet(\n provider: TransformResultProvider,\n moduleId: string,\n loc: Loc,\n contextLines: number = 2,\n): CodeSnippet | undefined {\n try {\n const importerFile = normalizeFilePath(moduleId)\n const res = provider.getTransformResult(moduleId)\n if (!res) return undefined\n\n const { code: transformedCode, originalCode } = res\n\n const sourceCode = originalCode ?? transformedCode\n const targetLine = loc.line // 1-indexed\n const targetCol = loc.column // 1-indexed\n\n if (targetLine < 1) return undefined\n\n const wantStart = Math.max(1, targetLine - contextLines)\n const wantEnd = targetLine + contextLines\n\n // Advance to wantStart\n let lineNum = 1\n let pos = 0\n while (lineNum < wantStart && pos < sourceCode.length) {\n const ch = sourceCode.charCodeAt(pos)\n if (ch === 10) {\n lineNum++\n } else if (ch === 13) {\n lineNum++\n if (\n pos + 1 < sourceCode.length &&\n sourceCode.charCodeAt(pos + 1) === 10\n )\n pos++\n }\n pos++\n }\n if (lineNum < wantStart) return undefined\n\n const lines: Array<string> = []\n let curLine = wantStart\n while (curLine <= wantEnd && pos <= sourceCode.length) {\n // Find end of current line\n let eol = pos\n while (eol < sourceCode.length) {\n const ch = sourceCode.charCodeAt(eol)\n if (ch === 10 || ch === 13) break\n eol++\n }\n lines.push(sourceCode.slice(pos, eol))\n curLine++\n if (eol < sourceCode.length) {\n if (\n sourceCode.charCodeAt(eol) === 13 &&\n eol + 1 < sourceCode.length &&\n sourceCode.charCodeAt(eol + 1) === 10\n ) {\n pos = eol + 2\n } else {\n pos = eol + 1\n }\n } else {\n pos = eol + 1\n }\n }\n\n if (targetLine > wantStart + lines.length - 1) return undefined\n\n const actualEnd = wantStart + lines.length - 1\n const gutterWidth = String(actualEnd).length\n\n const sourceFile = loc.file ?? importerFile\n const snippetLines: Array<string> = []\n for (let i = 0; i < lines.length; i++) {\n const ln = wantStart + i\n const lineContent = lines[i]!\n const lineNumStr = String(ln).padStart(gutterWidth, ' ')\n const marker = ln === targetLine ? '>' : ' '\n snippetLines.push(` ${marker} ${lineNumStr} | ${lineContent}`)\n\n if (ln === targetLine && targetCol > 0) {\n const padding = ' '.repeat(targetCol - 1)\n snippetLines.push(` ${' '.repeat(gutterWidth)} | ${padding}^`)\n }\n }\n\n return {\n lines: snippetLines,\n highlightLine: targetLine,\n location: `${sourceFile}:${targetLine}:${targetCol}`,\n }\n } catch {\n return undefined\n }\n}\n"],"names":[],"mappings":"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"}
@@ -64,4 +64,14 @@ export interface ViolationInfo {
64
64
  location: string;
65
65
  };
66
66
  }
67
+ /**
68
+ * Suggestion strings for server-only code leaking into client environments.
69
+ * Used by both `formatViolation` (terminal) and runtime mock modules (browser).
70
+ */
71
+ export declare const CLIENT_ENV_SUGGESTIONS: readonly ["Use createServerFn().handler(() => ...) to keep the logic on the server and call it from the client via an RPC bridge", "Use createServerOnlyFn(() => ...) to mark it as server-only (it will throw if accidentally called from the client)", "Use createIsomorphicFn().client(() => ...).server(() => ...) to provide separate client and server implementations", "Move the server-only import out of this file into a separate .server.ts module that is not imported by any client code"];
72
+ /**
73
+ * Suggestion strings for client-only code leaking into server environments.
74
+ * The JSX-specific suggestion is conditionally prepended by `formatViolation`.
75
+ */
76
+ export declare const SERVER_ENV_SUGGESTIONS: readonly ["Use createClientOnlyFn(() => ...) to mark it as client-only (returns undefined on the server)", "Use createIsomorphicFn().client(() => ...).server(() => ...) to provide separate client and server implementations", "Move the client-only import out of this file into a separate .client.ts module that is not imported by any server code"];
67
77
  export declare function formatViolation(info: ViolationInfo, root: string): string;