eslint-plugin-intlayer 9.3.2 → 9.3.3

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.
@@ -6,20 +6,27 @@ let _intlayer_config_callers = require("@intlayer/config/callers");
6
6
  //#region src/rules/enforceAdapterImport.ts
7
7
  const ADAPTER_SCOPE = "@intlayer/";
8
8
  /**
9
- * Original library specifier → the `@intlayer/*` adapter that replaces it.
9
+ * Original third-party library specifier → the `@intlayer/*` adapter that
10
+ * replaces it, e.g. `next-intl` → `@intlayer/next-intl`.
10
11
  *
11
12
  * Derived from the shared registry rather than hard-coded: every compat
12
13
  * descriptor lists both its original specifiers and their adapter equivalents in
13
14
  * `importSources`, so a library added to `@intlayer/config/callers` is covered
14
15
  * here for free.
15
16
  *
17
+ * Built from `COMPAT_CALLERS` only, never `ALL_CALLERS`. Intlayer's own packages
18
+ * are not a migration pair: `BASE_CALLERS` lists `intlayer` alongside
19
+ * `@intlayer/core` because `getIntlayer` is importable from both, and treating
20
+ * the scoped one as an "adapter" would tell users to rewrite ordinary
21
+ * `from 'intlayer'` imports.
22
+ *
16
23
  * Sub-paths line up by position within a descriptor — `next-intl/server` and
17
24
  * `@intlayer/next-intl/server` are listed together — so the mapping is built by
18
25
  * matching each original against the adapter that ends with the same tail.
19
26
  */
20
27
  const buildAdapterMap = () => {
21
28
  const adapterBySpecifier = /* @__PURE__ */ new Map();
22
- for (const descriptor of _intlayer_config_callers.ALL_CALLERS) {
29
+ for (const descriptor of _intlayer_config_callers.COMPAT_CALLERS) {
23
30
  const adapters = descriptor.importSources.filter((source) => source.startsWith(ADAPTER_SCOPE));
24
31
  if (adapters.length === 0) continue;
25
32
  const originals = descriptor.importSources.filter((source) => !source.startsWith(ADAPTER_SCOPE));
@@ -1 +1 @@
1
- {"version":3,"file":"enforceAdapterImport.cjs","names":["ALL_CALLERS","createRule","toAstNode"],"sources":["../../../src/rules/enforceAdapterImport.ts"],"sourcesContent":["import { ALL_CALLERS } from '@intlayer/config/callers';\nimport { type AstNode, toAstNode } from '../utils/ast';\nimport { createRule } from '../utils/createRule';\n\ntype MessageIds = 'useAdapterImport';\n\nconst ADAPTER_SCOPE = '@intlayer/';\n\n/**\n * Original library specifier → the `@intlayer/*` adapter that replaces it.\n *\n * Derived from the shared registry rather than hard-coded: every compat\n * descriptor lists both its original specifiers and their adapter equivalents in\n * `importSources`, so a library added to `@intlayer/config/callers` is covered\n * here for free.\n *\n * Sub-paths line up by position within a descriptor — `next-intl/server` and\n * `@intlayer/next-intl/server` are listed together — so the mapping is built by\n * matching each original against the adapter that ends with the same tail.\n */\nconst buildAdapterMap = (): Map<string, string> => {\n const adapterBySpecifier = new Map<string, string>();\n\n for (const descriptor of ALL_CALLERS) {\n const adapters = descriptor.importSources.filter((source) =>\n source.startsWith(ADAPTER_SCOPE)\n );\n\n if (adapters.length === 0) continue;\n\n const originals = descriptor.importSources.filter(\n (source) => !source.startsWith(ADAPTER_SCOPE)\n );\n\n for (const original of originals) {\n // `next-intl/server` → prefer `@intlayer/next-intl/server` over\n // `@intlayer/next-intl`, so match on the longest shared suffix.\n const adapter =\n adapters.find(\n (candidate) => candidate === `${ADAPTER_SCOPE}${original}`\n ) ??\n adapters.find((candidate) => candidate.endsWith(`/${original}`)) ??\n adapters[0];\n\n if (adapter && !adapterBySpecifier.has(original)) {\n adapterBySpecifier.set(original, adapter);\n }\n }\n }\n\n return adapterBySpecifier;\n};\n\nconst ADAPTER_BY_SPECIFIER = buildAdapterMap();\n\nexport const enforceAdapterImport = createRule<[], MessageIds>(\n 'enforce-adapter-import',\n {\n meta: {\n type: 'suggestion',\n fixable: 'code',\n docs: {\n description:\n 'Import compat-library APIs from their `@intlayer/*` adapter rather than the original package',\n },\n messages: {\n useAdapterImport:\n 'Import from `{{adapter}}` instead of `{{original}}`. The original specifier only resolves to Intlayer when the bundler alias is configured; the adapter always does.',\n },\n },\n\n create: (context) => {\n /** Report and rewrite a module specifier that has an adapter equivalent. */\n const checkSource = (node: AstNode | null | undefined): void => {\n if (!node) return;\n\n const original = node['value'];\n\n if (typeof original !== 'string') return;\n\n const adapter = ADAPTER_BY_SPECIFIER.get(original);\n\n if (!adapter) return;\n\n context.report({\n node: node as never,\n messageId: 'useAdapterImport',\n data: { adapter, original },\n fix: (fixer) => {\n const raw = (node['raw'] as string | undefined) ?? `'${original}'`;\n const quote = raw.startsWith('\"') ? '\"' : \"'\";\n\n return fixer.replaceText(\n node as never,\n `${quote}${adapter}${quote}`\n );\n },\n });\n };\n\n return {\n ImportDeclaration: (node) => {\n checkSource(toAstNode(node)['source'] as AstNode);\n },\n\n ExportNamedDeclaration: (node) => {\n checkSource(toAstNode(node)['source'] as AstNode);\n },\n\n ExportAllDeclaration: (node) => {\n checkSource(toAstNode(node)['source'] as AstNode);\n },\n\n ImportExpression: (node) => {\n checkSource(toAstNode(node)['source'] as AstNode);\n },\n };\n },\n }\n);\n"],"mappings":";;;;;;AAMA,MAAM,gBAAgB;;;;;;;;;;;;;AActB,MAAM,wBAA6C;CACjD,MAAM,qCAAqB,IAAI,IAAoB;CAEnD,KAAK,MAAM,cAAcA,sCAAa;EACpC,MAAM,WAAW,WAAW,cAAc,QAAQ,WAChD,OAAO,WAAW,aAAa,CACjC;EAEA,IAAI,SAAS,WAAW,GAAG;EAE3B,MAAM,YAAY,WAAW,cAAc,QACxC,WAAW,CAAC,OAAO,WAAW,aAAa,CAC9C;EAEA,KAAK,MAAM,YAAY,WAAW;GAGhC,MAAM,UACJ,SAAS,MACN,cAAc,cAAc,GAAG,gBAAgB,UAClD,KACA,SAAS,MAAM,cAAc,UAAU,SAAS,IAAI,UAAU,CAAC,KAC/D,SAAS;GAEX,IAAI,WAAW,CAAC,mBAAmB,IAAI,QAAQ,GAC7C,mBAAmB,IAAI,UAAU,OAAO;EAE5C;CACF;CAEA,OAAO;AACT;AAEA,MAAM,uBAAuB,gBAAgB;AAE7C,MAAa,uBAAuBC,oCAClC,0BACA;CACE,MAAM;EACJ,MAAM;EACN,SAAS;EACT,MAAM,EACJ,aACE,+FACJ;EACA,UAAU,EACR,kBACE,uKACJ;CACF;CAEA,SAAS,YAAY;;EAEnB,MAAM,eAAe,SAA2C;GAC9D,IAAI,CAAC,MAAM;GAEX,MAAM,WAAW,KAAK;GAEtB,IAAI,OAAO,aAAa,UAAU;GAElC,MAAM,UAAU,qBAAqB,IAAI,QAAQ;GAEjD,IAAI,CAAC,SAAS;GAEd,QAAQ,OAAO;IACP;IACN,WAAW;IACX,MAAM;KAAE;KAAS;IAAS;IAC1B,MAAM,UAAU;KAEd,MAAM,SADO,KAAK,UAAiC,IAAI,SAAS,GAC/C,CAAC,WAAW,IAAG,IAAI,OAAM;KAE1C,OAAO,MAAM,YACX,MACA,GAAG,QAAQ,UAAU,OACvB;IACF;GACF,CAAC;EACH;EAEA,OAAO;GACL,oBAAoB,SAAS;IAC3B,YAAYC,4BAAU,IAAI,CAAC,CAAC,SAAoB;GAClD;GAEA,yBAAyB,SAAS;IAChC,YAAYA,4BAAU,IAAI,CAAC,CAAC,SAAoB;GAClD;GAEA,uBAAuB,SAAS;IAC9B,YAAYA,4BAAU,IAAI,CAAC,CAAC,SAAoB;GAClD;GAEA,mBAAmB,SAAS;IAC1B,YAAYA,4BAAU,IAAI,CAAC,CAAC,SAAoB;GAClD;EACF;CACF;AACF,CACF"}
1
+ {"version":3,"file":"enforceAdapterImport.cjs","names":["COMPAT_CALLERS","createRule","toAstNode"],"sources":["../../../src/rules/enforceAdapterImport.ts"],"sourcesContent":["import { COMPAT_CALLERS } from '@intlayer/config/callers';\nimport { type AstNode, toAstNode } from '../utils/ast';\nimport { createRule } from '../utils/createRule';\n\ntype MessageIds = 'useAdapterImport';\n\nconst ADAPTER_SCOPE = '@intlayer/';\n\n/**\n * Original third-party library specifier → the `@intlayer/*` adapter that\n * replaces it, e.g. `next-intl` → `@intlayer/next-intl`.\n *\n * Derived from the shared registry rather than hard-coded: every compat\n * descriptor lists both its original specifiers and their adapter equivalents in\n * `importSources`, so a library added to `@intlayer/config/callers` is covered\n * here for free.\n *\n * Built from `COMPAT_CALLERS` only, never `ALL_CALLERS`. Intlayer's own packages\n * are not a migration pair: `BASE_CALLERS` lists `intlayer` alongside\n * `@intlayer/core` because `getIntlayer` is importable from both, and treating\n * the scoped one as an \"adapter\" would tell users to rewrite ordinary\n * `from 'intlayer'` imports.\n *\n * Sub-paths line up by position within a descriptor — `next-intl/server` and\n * `@intlayer/next-intl/server` are listed together — so the mapping is built by\n * matching each original against the adapter that ends with the same tail.\n */\nconst buildAdapterMap = (): Map<string, string> => {\n const adapterBySpecifier = new Map<string, string>();\n\n for (const descriptor of COMPAT_CALLERS) {\n const adapters = descriptor.importSources.filter((source) =>\n source.startsWith(ADAPTER_SCOPE)\n );\n\n if (adapters.length === 0) continue;\n\n const originals = descriptor.importSources.filter(\n (source) => !source.startsWith(ADAPTER_SCOPE)\n );\n\n for (const original of originals) {\n // `next-intl/server` → prefer `@intlayer/next-intl/server` over\n // `@intlayer/next-intl`, so match on the longest shared suffix.\n const adapter =\n adapters.find(\n (candidate) => candidate === `${ADAPTER_SCOPE}${original}`\n ) ??\n adapters.find((candidate) => candidate.endsWith(`/${original}`)) ??\n adapters[0];\n\n if (adapter && !adapterBySpecifier.has(original)) {\n adapterBySpecifier.set(original, adapter);\n }\n }\n }\n\n return adapterBySpecifier;\n};\n\nconst ADAPTER_BY_SPECIFIER = buildAdapterMap();\n\nexport const enforceAdapterImport = createRule<[], MessageIds>(\n 'enforce-adapter-import',\n {\n meta: {\n type: 'suggestion',\n fixable: 'code',\n docs: {\n description:\n 'Import compat-library APIs from their `@intlayer/*` adapter rather than the original package',\n },\n messages: {\n useAdapterImport:\n 'Import from `{{adapter}}` instead of `{{original}}`. The original specifier only resolves to Intlayer when the bundler alias is configured; the adapter always does.',\n },\n },\n\n create: (context) => {\n /** Report and rewrite a module specifier that has an adapter equivalent. */\n const checkSource = (node: AstNode | null | undefined): void => {\n if (!node) return;\n\n const original = node['value'];\n\n if (typeof original !== 'string') return;\n\n const adapter = ADAPTER_BY_SPECIFIER.get(original);\n\n if (!adapter) return;\n\n context.report({\n node: node as never,\n messageId: 'useAdapterImport',\n data: { adapter, original },\n fix: (fixer) => {\n const raw = (node['raw'] as string | undefined) ?? `'${original}'`;\n const quote = raw.startsWith('\"') ? '\"' : \"'\";\n\n return fixer.replaceText(\n node as never,\n `${quote}${adapter}${quote}`\n );\n },\n });\n };\n\n return {\n ImportDeclaration: (node) => {\n checkSource(toAstNode(node)['source'] as AstNode);\n },\n\n ExportNamedDeclaration: (node) => {\n checkSource(toAstNode(node)['source'] as AstNode);\n },\n\n ExportAllDeclaration: (node) => {\n checkSource(toAstNode(node)['source'] as AstNode);\n },\n\n ImportExpression: (node) => {\n checkSource(toAstNode(node)['source'] as AstNode);\n },\n };\n },\n }\n);\n"],"mappings":";;;;;;AAMA,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;AAqBtB,MAAM,wBAA6C;CACjD,MAAM,qCAAqB,IAAI,IAAoB;CAEnD,KAAK,MAAM,cAAcA,yCAAgB;EACvC,MAAM,WAAW,WAAW,cAAc,QAAQ,WAChD,OAAO,WAAW,aAAa,CACjC;EAEA,IAAI,SAAS,WAAW,GAAG;EAE3B,MAAM,YAAY,WAAW,cAAc,QACxC,WAAW,CAAC,OAAO,WAAW,aAAa,CAC9C;EAEA,KAAK,MAAM,YAAY,WAAW;GAGhC,MAAM,UACJ,SAAS,MACN,cAAc,cAAc,GAAG,gBAAgB,UAClD,KACA,SAAS,MAAM,cAAc,UAAU,SAAS,IAAI,UAAU,CAAC,KAC/D,SAAS;GAEX,IAAI,WAAW,CAAC,mBAAmB,IAAI,QAAQ,GAC7C,mBAAmB,IAAI,UAAU,OAAO;EAE5C;CACF;CAEA,OAAO;AACT;AAEA,MAAM,uBAAuB,gBAAgB;AAE7C,MAAa,uBAAuBC,oCAClC,0BACA;CACE,MAAM;EACJ,MAAM;EACN,SAAS;EACT,MAAM,EACJ,aACE,+FACJ;EACA,UAAU,EACR,kBACE,uKACJ;CACF;CAEA,SAAS,YAAY;;EAEnB,MAAM,eAAe,SAA2C;GAC9D,IAAI,CAAC,MAAM;GAEX,MAAM,WAAW,KAAK;GAEtB,IAAI,OAAO,aAAa,UAAU;GAElC,MAAM,UAAU,qBAAqB,IAAI,QAAQ;GAEjD,IAAI,CAAC,SAAS;GAEd,QAAQ,OAAO;IACP;IACN,WAAW;IACX,MAAM;KAAE;KAAS;IAAS;IAC1B,MAAM,UAAU;KAEd,MAAM,SADO,KAAK,UAAiC,IAAI,SAAS,GAC/C,CAAC,WAAW,IAAG,IAAI,OAAM;KAE1C,OAAO,MAAM,YACX,MACA,GAAG,QAAQ,UAAU,OACvB;IACF;GACF,CAAC;EACH;EAEA,OAAO;GACL,oBAAoB,SAAS;IAC3B,YAAYC,4BAAU,IAAI,CAAC,CAAC,SAAoB;GAClD;GAEA,yBAAyB,SAAS;IAChC,YAAYA,4BAAU,IAAI,CAAC,CAAC,SAAoB;GAClD;GAEA,uBAAuB,SAAS;IAC9B,YAAYA,4BAAU,IAAI,CAAC,CAAC,SAAoB;GAClD;GAEA,mBAAmB,SAAS;IAC1B,YAAYA,4BAAU,IAAI,CAAC,CAAC,SAAoB;GAClD;EACF;CACF;AACF,CACF"}
@@ -25,8 +25,8 @@ const staticDictionaryKey = require_utils_createRule.createRule("static-dictiona
25
25
  docs: { description: "Require the dictionary key of an Intlayer content read to be a static string literal" },
26
26
  messages: {
27
27
  dynamicKey: "The dictionary key passed to `{{caller}}` must be a string literal. A computed key is invisible to the Intlayer compiler, which then skips the optimize pass and bundles every dictionary.",
28
- dynamicTemplateKey: "The dictionary key passed to `{{caller}}` interpolates a value. Use a string literal — an interpolated key is invisible to the Intlayer compiler, which then skips the optimize pass and bundles every dictionary.",
29
- dynamicJsxKey: "The `{{attribute}}` of `<{{caller}}>` must be a static string. A computed value is invisible to the Intlayer compiler, which then cannot resolve the dictionary at build time."
28
+ dynamicTemplateKey: "The dictionary key passed to `{{caller}}` interpolates a value. An interpolated key is invisible to the Intlayer compiler, which then skips the optimize pass and bundles every dictionary. Use a string literal, or variant (https://intlayer.org/doc/concept/variants) instead.",
29
+ dynamicJsxKey: "The `{{attribute}}` of `<{{caller}}>` must be a static string. A computed value is invisible to the Intlayer compiler, which then cannot resolve the dictionary at build time. Use a static string, or variant (https://intlayer.org/doc/concept/variants) instead"
30
30
  }
31
31
  },
32
32
  create: (context) => {
@@ -1 +1 @@
1
- {"version":3,"file":"staticDictionaryKey.cjs","names":["createRule","getActiveDescriptors","collectImportSources","toAstNode","matchCaller","matchJsxCaller","getJsxElementName","getJsxAttributeName","getJsxAttributeValueNode","getStaticStringValue"],"sources":["../../../src/rules/staticDictionaryKey.ts"],"sourcesContent":["import type { CallerDescriptor } from '@intlayer/config/callers';\nimport {\n type AstNode,\n getJsxAttributeName,\n getJsxAttributeValueNode,\n getJsxElementName,\n getStaticStringValue,\n toAstNode,\n} from '../utils/ast';\nimport {\n type CallerMatch,\n collectImportSources,\n getActiveDescriptors,\n matchCaller,\n matchJsxCaller,\n} from '../utils/callerMatcher';\nimport { createRule } from '../utils/createRule';\n\ntype MessageIds = 'dynamicKey' | 'dynamicTemplateKey' | 'dynamicJsxKey';\n\n/**\n * True when the call may legitimately omit its namespace — next-intl's\n * `useTranslations()` root scope derives the dictionary from each message id\n * instead, so an absent argument is not a violation.\n *\n * @param descriptor - The matched registry entry.\n */\nconst allowsMissingNamespace = (descriptor: CallerDescriptor): boolean =>\n descriptor.allowRootScope === true;\n\n/**\n * Distinguish an interpolated template from any other runtime expression, so\n * the report can name the actual problem.\n *\n * @param node - The namespace node.\n */\nconst isInterpolatedTemplate = (node: AstNode): boolean =>\n node.type === 'TemplateLiteral' &&\n ((node['expressions'] as unknown[] | undefined)?.length ?? 0) > 0;\n\nexport const staticDictionaryKey = createRule<[], MessageIds>(\n 'static-dictionary-key',\n {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Require the dictionary key of an Intlayer content read to be a static string literal',\n },\n messages: {\n dynamicKey:\n 'The dictionary key passed to `{{caller}}` must be a string literal. A computed key is invisible to the Intlayer compiler, which then skips the optimize pass and bundles every dictionary.',\n dynamicTemplateKey:\n 'The dictionary key passed to `{{caller}}` interpolates a value. Use a string literal — an interpolated key is invisible to the Intlayer compiler, which then skips the optimize pass and bundles every dictionary.',\n dynamicJsxKey:\n 'The `{{attribute}}` of `<{{caller}}>` must be a static string. A computed value is invisible to the Intlayer compiler, which then cannot resolve the dictionary at build time.',\n },\n },\n\n create: (context) => {\n let activeDescriptors = getActiveDescriptors(new Set());\n\n /** Report the namespace of a matched call when it is not static. */\n const reportDynamicNamespace = (match: CallerMatch): void => {\n const { descriptor, namespace } = match;\n\n if (!namespace) return;\n\n // A `fixed` namespace is a compile-time constant by construction.\n if (namespace.source.from === 'fixed') return;\n\n if (!namespace.node) {\n // Nothing was passed at all — only a problem for callers that require it.\n return;\n }\n\n if (namespace.value !== null) return;\n\n context.report({\n node: namespace.node as never,\n messageId: isInterpolatedTemplate(namespace.node)\n ? 'dynamicTemplateKey'\n : 'dynamicKey',\n data: { caller: descriptor.callerName },\n });\n };\n\n return {\n Program: (node) => {\n activeDescriptors = getActiveDescriptors(\n collectImportSources(toAstNode(node))\n );\n },\n\n CallExpression: (node) => {\n const match = matchCaller(toAstNode(node), activeDescriptors);\n\n if (!match) return;\n\n if (!match.namespace && allowsMissingNamespace(match.descriptor)) {\n return;\n }\n\n reportDynamicNamespace(match);\n },\n\n TaggedTemplateExpression: (node) => {\n const match = matchCaller(toAstNode(node), activeDescriptors);\n\n if (match) reportDynamicNamespace(match);\n },\n\n JSXOpeningElement: (node) => {\n const astNode = toAstNode(node);\n const descriptor = matchJsxCaller(\n getJsxElementName(astNode),\n activeDescriptors\n );\n\n if (!descriptor) return;\n\n const attributes =\n (astNode['attributes'] as AstNode[] | undefined) ?? [];\n\n const targetAttributes = [\n descriptor.jsxIdAttribute,\n descriptor.jsxNamespaceAttribute,\n ].filter((name): name is string => typeof name === 'string');\n\n for (const attribute of attributes) {\n const attributeName = getJsxAttributeName(attribute);\n\n if (!attributeName || !targetAttributes.includes(attributeName)) {\n continue;\n }\n\n const valueNode = getJsxAttributeValueNode(attribute);\n\n if (!valueNode) continue;\n\n if (getStaticStringValue(valueNode) !== null) continue;\n\n context.report({\n node: attribute as never,\n messageId: 'dynamicJsxKey',\n data: {\n attribute: attributeName,\n caller: descriptor.callerName,\n },\n });\n }\n },\n };\n },\n }\n);\n"],"mappings":";;;;;;;;;;;;;AA2BA,MAAM,0BAA0B,eAC9B,WAAW,mBAAmB;;;;;;;AAQhC,MAAM,0BAA0B,SAC9B,KAAK,SAAS,sBACZ,KAAK,cAAc,EAA4B,UAAU,KAAK;AAElE,MAAa,sBAAsBA,oCACjC,yBACA;CACE,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,uFACJ;EACA,UAAU;GACR,YACE;GACF,oBACE;GACF,eACE;EACJ;CACF;CAEA,SAAS,YAAY;EACnB,IAAI,oBAAoBC,iEAAqB,IAAI,IAAI,CAAC;;EAGtD,MAAM,0BAA0B,UAA6B;GAC3D,MAAM,EAAE,YAAY,cAAc;GAElC,IAAI,CAAC,WAAW;GAGhB,IAAI,UAAU,OAAO,SAAS,SAAS;GAEvC,IAAI,CAAC,UAAU,MAEb;GAGF,IAAI,UAAU,UAAU,MAAM;GAE9B,QAAQ,OAAO;IACb,MAAM,UAAU;IAChB,WAAW,uBAAuB,UAAU,IAAI,IAC5C,uBACA;IACJ,MAAM,EAAE,QAAQ,WAAW,WAAW;GACxC,CAAC;EACH;EAEA,OAAO;GACL,UAAU,SAAS;IACjB,oBAAoBA,iDAClBC,iDAAqBC,4BAAU,IAAI,CAAC,CACtC;GACF;GAEA,iBAAiB,SAAS;IACxB,MAAM,QAAQC,wCAAYD,4BAAU,IAAI,GAAG,iBAAiB;IAE5D,IAAI,CAAC,OAAO;IAEZ,IAAI,CAAC,MAAM,aAAa,uBAAuB,MAAM,UAAU,GAC7D;IAGF,uBAAuB,KAAK;GAC9B;GAEA,2BAA2B,SAAS;IAClC,MAAM,QAAQC,wCAAYD,4BAAU,IAAI,GAAG,iBAAiB;IAE5D,IAAI,OAAO,uBAAuB,KAAK;GACzC;GAEA,oBAAoB,SAAS;IAC3B,MAAM,UAAUA,4BAAU,IAAI;IAC9B,MAAM,aAAaE,2CACjBC,oCAAkB,OAAO,GACzB,iBACF;IAEA,IAAI,CAAC,YAAY;IAEjB,MAAM,aACH,QAAQ,iBAA2C,CAAC;IAEvD,MAAM,mBAAmB,CACvB,WAAW,gBACX,WAAW,qBACb,CAAC,CAAC,QAAQ,SAAyB,OAAO,SAAS,QAAQ;IAE3D,KAAK,MAAM,aAAa,YAAY;KAClC,MAAM,gBAAgBC,sCAAoB,SAAS;KAEnD,IAAI,CAAC,iBAAiB,CAAC,iBAAiB,SAAS,aAAa,GAC5D;KAGF,MAAM,YAAYC,2CAAyB,SAAS;KAEpD,IAAI,CAAC,WAAW;KAEhB,IAAIC,uCAAqB,SAAS,MAAM,MAAM;KAE9C,QAAQ,OAAO;MACb,MAAM;MACN,WAAW;MACX,MAAM;OACJ,WAAW;OACX,QAAQ,WAAW;MACrB;KACF,CAAC;IACH;GACF;EACF;CACF;AACF,CACF"}
1
+ {"version":3,"file":"staticDictionaryKey.cjs","names":["createRule","getActiveDescriptors","collectImportSources","toAstNode","matchCaller","matchJsxCaller","getJsxElementName","getJsxAttributeName","getJsxAttributeValueNode","getStaticStringValue"],"sources":["../../../src/rules/staticDictionaryKey.ts"],"sourcesContent":["import type { CallerDescriptor } from '@intlayer/config/callers';\nimport {\n type AstNode,\n getJsxAttributeName,\n getJsxAttributeValueNode,\n getJsxElementName,\n getStaticStringValue,\n toAstNode,\n} from '../utils/ast';\nimport {\n type CallerMatch,\n collectImportSources,\n getActiveDescriptors,\n matchCaller,\n matchJsxCaller,\n} from '../utils/callerMatcher';\nimport { createRule } from '../utils/createRule';\n\ntype MessageIds = 'dynamicKey' | 'dynamicTemplateKey' | 'dynamicJsxKey';\n\n/**\n * True when the call may legitimately omit its namespace — next-intl's\n * `useTranslations()` root scope derives the dictionary from each message id\n * instead, so an absent argument is not a violation.\n *\n * @param descriptor - The matched registry entry.\n */\nconst allowsMissingNamespace = (descriptor: CallerDescriptor): boolean =>\n descriptor.allowRootScope === true;\n\n/**\n * Distinguish an interpolated template from any other runtime expression, so\n * the report can name the actual problem.\n *\n * @param node - The namespace node.\n */\nconst isInterpolatedTemplate = (node: AstNode): boolean =>\n node.type === 'TemplateLiteral' &&\n ((node['expressions'] as unknown[] | undefined)?.length ?? 0) > 0;\n\nexport const staticDictionaryKey = createRule<[], MessageIds>(\n 'static-dictionary-key',\n {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Require the dictionary key of an Intlayer content read to be a static string literal',\n },\n messages: {\n dynamicKey:\n 'The dictionary key passed to `{{caller}}` must be a string literal. A computed key is invisible to the Intlayer compiler, which then skips the optimize pass and bundles every dictionary.',\n dynamicTemplateKey:\n 'The dictionary key passed to `{{caller}}` interpolates a value. An interpolated key is invisible to the Intlayer compiler, which then skips the optimize pass and bundles every dictionary. Use a string literal, or variant (https://intlayer.org/doc/concept/variants) instead.',\n dynamicJsxKey:\n 'The `{{attribute}}` of `<{{caller}}>` must be a static string. A computed value is invisible to the Intlayer compiler, which then cannot resolve the dictionary at build time. Use a static string, or variant (https://intlayer.org/doc/concept/variants) instead',\n },\n },\n\n create: (context) => {\n let activeDescriptors = getActiveDescriptors(new Set());\n\n /** Report the namespace of a matched call when it is not static. */\n const reportDynamicNamespace = (match: CallerMatch): void => {\n const { descriptor, namespace } = match;\n\n if (!namespace) return;\n\n // A `fixed` namespace is a compile-time constant by construction.\n if (namespace.source.from === 'fixed') return;\n\n if (!namespace.node) {\n // Nothing was passed at all — only a problem for callers that require it.\n return;\n }\n\n if (namespace.value !== null) return;\n\n context.report({\n node: namespace.node as never,\n messageId: isInterpolatedTemplate(namespace.node)\n ? 'dynamicTemplateKey'\n : 'dynamicKey',\n data: { caller: descriptor.callerName },\n });\n };\n\n return {\n Program: (node) => {\n activeDescriptors = getActiveDescriptors(\n collectImportSources(toAstNode(node))\n );\n },\n\n CallExpression: (node) => {\n const match = matchCaller(toAstNode(node), activeDescriptors);\n\n if (!match) return;\n\n if (!match.namespace && allowsMissingNamespace(match.descriptor)) {\n return;\n }\n\n reportDynamicNamespace(match);\n },\n\n TaggedTemplateExpression: (node) => {\n const match = matchCaller(toAstNode(node), activeDescriptors);\n\n if (match) reportDynamicNamespace(match);\n },\n\n JSXOpeningElement: (node) => {\n const astNode = toAstNode(node);\n const descriptor = matchJsxCaller(\n getJsxElementName(astNode),\n activeDescriptors\n );\n\n if (!descriptor) return;\n\n const attributes =\n (astNode['attributes'] as AstNode[] | undefined) ?? [];\n\n const targetAttributes = [\n descriptor.jsxIdAttribute,\n descriptor.jsxNamespaceAttribute,\n ].filter((name): name is string => typeof name === 'string');\n\n for (const attribute of attributes) {\n const attributeName = getJsxAttributeName(attribute);\n\n if (!attributeName || !targetAttributes.includes(attributeName)) {\n continue;\n }\n\n const valueNode = getJsxAttributeValueNode(attribute);\n\n if (!valueNode) continue;\n\n if (getStaticStringValue(valueNode) !== null) continue;\n\n context.report({\n node: attribute as never,\n messageId: 'dynamicJsxKey',\n data: {\n attribute: attributeName,\n caller: descriptor.callerName,\n },\n });\n }\n },\n };\n },\n }\n);\n"],"mappings":";;;;;;;;;;;;;AA2BA,MAAM,0BAA0B,eAC9B,WAAW,mBAAmB;;;;;;;AAQhC,MAAM,0BAA0B,SAC9B,KAAK,SAAS,sBACZ,KAAK,cAAc,EAA4B,UAAU,KAAK;AAElE,MAAa,sBAAsBA,oCACjC,yBACA;CACE,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,uFACJ;EACA,UAAU;GACR,YACE;GACF,oBACE;GACF,eACE;EACJ;CACF;CAEA,SAAS,YAAY;EACnB,IAAI,oBAAoBC,iEAAqB,IAAI,IAAI,CAAC;;EAGtD,MAAM,0BAA0B,UAA6B;GAC3D,MAAM,EAAE,YAAY,cAAc;GAElC,IAAI,CAAC,WAAW;GAGhB,IAAI,UAAU,OAAO,SAAS,SAAS;GAEvC,IAAI,CAAC,UAAU,MAEb;GAGF,IAAI,UAAU,UAAU,MAAM;GAE9B,QAAQ,OAAO;IACb,MAAM,UAAU;IAChB,WAAW,uBAAuB,UAAU,IAAI,IAC5C,uBACA;IACJ,MAAM,EAAE,QAAQ,WAAW,WAAW;GACxC,CAAC;EACH;EAEA,OAAO;GACL,UAAU,SAAS;IACjB,oBAAoBA,iDAClBC,iDAAqBC,4BAAU,IAAI,CAAC,CACtC;GACF;GAEA,iBAAiB,SAAS;IACxB,MAAM,QAAQC,wCAAYD,4BAAU,IAAI,GAAG,iBAAiB;IAE5D,IAAI,CAAC,OAAO;IAEZ,IAAI,CAAC,MAAM,aAAa,uBAAuB,MAAM,UAAU,GAC7D;IAGF,uBAAuB,KAAK;GAC9B;GAEA,2BAA2B,SAAS;IAClC,MAAM,QAAQC,wCAAYD,4BAAU,IAAI,GAAG,iBAAiB;IAE5D,IAAI,OAAO,uBAAuB,KAAK;GACzC;GAEA,oBAAoB,SAAS;IAC3B,MAAM,UAAUA,4BAAU,IAAI;IAC9B,MAAM,aAAaE,2CACjBC,oCAAkB,OAAO,GACzB,iBACF;IAEA,IAAI,CAAC,YAAY;IAEjB,MAAM,aACH,QAAQ,iBAA2C,CAAC;IAEvD,MAAM,mBAAmB,CACvB,WAAW,gBACX,WAAW,qBACb,CAAC,CAAC,QAAQ,SAAyB,OAAO,SAAS,QAAQ;IAE3D,KAAK,MAAM,aAAa,YAAY;KAClC,MAAM,gBAAgBC,sCAAoB,SAAS;KAEnD,IAAI,CAAC,iBAAiB,CAAC,iBAAiB,SAAS,aAAa,GAC5D;KAGF,MAAM,YAAYC,2CAAyB,SAAS;KAEpD,IAAI,CAAC,WAAW;KAEhB,IAAIC,uCAAqB,SAAS,MAAM,MAAM;KAE9C,QAAQ,OAAO;MACb,MAAM;MACN,WAAW;MACX,MAAM;OACJ,WAAW;OACX,QAAQ,WAAW;MACrB;KACF,CAAC;IACH;GACF;EACF;CACF;AACF,CACF"}
@@ -1,24 +1,31 @@
1
1
  import { toAstNode } from "../utils/ast.mjs";
2
2
  import { createRule } from "../utils/createRule.mjs";
3
- import { ALL_CALLERS } from "@intlayer/config/callers";
3
+ import { COMPAT_CALLERS } from "@intlayer/config/callers";
4
4
 
5
5
  //#region src/rules/enforceAdapterImport.ts
6
6
  const ADAPTER_SCOPE = "@intlayer/";
7
7
  /**
8
- * Original library specifier → the `@intlayer/*` adapter that replaces it.
8
+ * Original third-party library specifier → the `@intlayer/*` adapter that
9
+ * replaces it, e.g. `next-intl` → `@intlayer/next-intl`.
9
10
  *
10
11
  * Derived from the shared registry rather than hard-coded: every compat
11
12
  * descriptor lists both its original specifiers and their adapter equivalents in
12
13
  * `importSources`, so a library added to `@intlayer/config/callers` is covered
13
14
  * here for free.
14
15
  *
16
+ * Built from `COMPAT_CALLERS` only, never `ALL_CALLERS`. Intlayer's own packages
17
+ * are not a migration pair: `BASE_CALLERS` lists `intlayer` alongside
18
+ * `@intlayer/core` because `getIntlayer` is importable from both, and treating
19
+ * the scoped one as an "adapter" would tell users to rewrite ordinary
20
+ * `from 'intlayer'` imports.
21
+ *
15
22
  * Sub-paths line up by position within a descriptor — `next-intl/server` and
16
23
  * `@intlayer/next-intl/server` are listed together — so the mapping is built by
17
24
  * matching each original against the adapter that ends with the same tail.
18
25
  */
19
26
  const buildAdapterMap = () => {
20
27
  const adapterBySpecifier = /* @__PURE__ */ new Map();
21
- for (const descriptor of ALL_CALLERS) {
28
+ for (const descriptor of COMPAT_CALLERS) {
22
29
  const adapters = descriptor.importSources.filter((source) => source.startsWith(ADAPTER_SCOPE));
23
30
  if (adapters.length === 0) continue;
24
31
  const originals = descriptor.importSources.filter((source) => !source.startsWith(ADAPTER_SCOPE));
@@ -1 +1 @@
1
- {"version":3,"file":"enforceAdapterImport.mjs","names":[],"sources":["../../../src/rules/enforceAdapterImport.ts"],"sourcesContent":["import { ALL_CALLERS } from '@intlayer/config/callers';\nimport { type AstNode, toAstNode } from '../utils/ast';\nimport { createRule } from '../utils/createRule';\n\ntype MessageIds = 'useAdapterImport';\n\nconst ADAPTER_SCOPE = '@intlayer/';\n\n/**\n * Original library specifier → the `@intlayer/*` adapter that replaces it.\n *\n * Derived from the shared registry rather than hard-coded: every compat\n * descriptor lists both its original specifiers and their adapter equivalents in\n * `importSources`, so a library added to `@intlayer/config/callers` is covered\n * here for free.\n *\n * Sub-paths line up by position within a descriptor — `next-intl/server` and\n * `@intlayer/next-intl/server` are listed together — so the mapping is built by\n * matching each original against the adapter that ends with the same tail.\n */\nconst buildAdapterMap = (): Map<string, string> => {\n const adapterBySpecifier = new Map<string, string>();\n\n for (const descriptor of ALL_CALLERS) {\n const adapters = descriptor.importSources.filter((source) =>\n source.startsWith(ADAPTER_SCOPE)\n );\n\n if (adapters.length === 0) continue;\n\n const originals = descriptor.importSources.filter(\n (source) => !source.startsWith(ADAPTER_SCOPE)\n );\n\n for (const original of originals) {\n // `next-intl/server` → prefer `@intlayer/next-intl/server` over\n // `@intlayer/next-intl`, so match on the longest shared suffix.\n const adapter =\n adapters.find(\n (candidate) => candidate === `${ADAPTER_SCOPE}${original}`\n ) ??\n adapters.find((candidate) => candidate.endsWith(`/${original}`)) ??\n adapters[0];\n\n if (adapter && !adapterBySpecifier.has(original)) {\n adapterBySpecifier.set(original, adapter);\n }\n }\n }\n\n return adapterBySpecifier;\n};\n\nconst ADAPTER_BY_SPECIFIER = buildAdapterMap();\n\nexport const enforceAdapterImport = createRule<[], MessageIds>(\n 'enforce-adapter-import',\n {\n meta: {\n type: 'suggestion',\n fixable: 'code',\n docs: {\n description:\n 'Import compat-library APIs from their `@intlayer/*` adapter rather than the original package',\n },\n messages: {\n useAdapterImport:\n 'Import from `{{adapter}}` instead of `{{original}}`. The original specifier only resolves to Intlayer when the bundler alias is configured; the adapter always does.',\n },\n },\n\n create: (context) => {\n /** Report and rewrite a module specifier that has an adapter equivalent. */\n const checkSource = (node: AstNode | null | undefined): void => {\n if (!node) return;\n\n const original = node['value'];\n\n if (typeof original !== 'string') return;\n\n const adapter = ADAPTER_BY_SPECIFIER.get(original);\n\n if (!adapter) return;\n\n context.report({\n node: node as never,\n messageId: 'useAdapterImport',\n data: { adapter, original },\n fix: (fixer) => {\n const raw = (node['raw'] as string | undefined) ?? `'${original}'`;\n const quote = raw.startsWith('\"') ? '\"' : \"'\";\n\n return fixer.replaceText(\n node as never,\n `${quote}${adapter}${quote}`\n );\n },\n });\n };\n\n return {\n ImportDeclaration: (node) => {\n checkSource(toAstNode(node)['source'] as AstNode);\n },\n\n ExportNamedDeclaration: (node) => {\n checkSource(toAstNode(node)['source'] as AstNode);\n },\n\n ExportAllDeclaration: (node) => {\n checkSource(toAstNode(node)['source'] as AstNode);\n },\n\n ImportExpression: (node) => {\n checkSource(toAstNode(node)['source'] as AstNode);\n },\n };\n },\n }\n);\n"],"mappings":";;;;;AAMA,MAAM,gBAAgB;;;;;;;;;;;;;AActB,MAAM,wBAA6C;CACjD,MAAM,qCAAqB,IAAI,IAAoB;CAEnD,KAAK,MAAM,cAAc,aAAa;EACpC,MAAM,WAAW,WAAW,cAAc,QAAQ,WAChD,OAAO,WAAW,aAAa,CACjC;EAEA,IAAI,SAAS,WAAW,GAAG;EAE3B,MAAM,YAAY,WAAW,cAAc,QACxC,WAAW,CAAC,OAAO,WAAW,aAAa,CAC9C;EAEA,KAAK,MAAM,YAAY,WAAW;GAGhC,MAAM,UACJ,SAAS,MACN,cAAc,cAAc,GAAG,gBAAgB,UAClD,KACA,SAAS,MAAM,cAAc,UAAU,SAAS,IAAI,UAAU,CAAC,KAC/D,SAAS;GAEX,IAAI,WAAW,CAAC,mBAAmB,IAAI,QAAQ,GAC7C,mBAAmB,IAAI,UAAU,OAAO;EAE5C;CACF;CAEA,OAAO;AACT;AAEA,MAAM,uBAAuB,gBAAgB;AAE7C,MAAa,uBAAuB,WAClC,0BACA;CACE,MAAM;EACJ,MAAM;EACN,SAAS;EACT,MAAM,EACJ,aACE,+FACJ;EACA,UAAU,EACR,kBACE,uKACJ;CACF;CAEA,SAAS,YAAY;;EAEnB,MAAM,eAAe,SAA2C;GAC9D,IAAI,CAAC,MAAM;GAEX,MAAM,WAAW,KAAK;GAEtB,IAAI,OAAO,aAAa,UAAU;GAElC,MAAM,UAAU,qBAAqB,IAAI,QAAQ;GAEjD,IAAI,CAAC,SAAS;GAEd,QAAQ,OAAO;IACP;IACN,WAAW;IACX,MAAM;KAAE;KAAS;IAAS;IAC1B,MAAM,UAAU;KAEd,MAAM,SADO,KAAK,UAAiC,IAAI,SAAS,GAC/C,CAAC,WAAW,IAAG,IAAI,OAAM;KAE1C,OAAO,MAAM,YACX,MACA,GAAG,QAAQ,UAAU,OACvB;IACF;GACF,CAAC;EACH;EAEA,OAAO;GACL,oBAAoB,SAAS;IAC3B,YAAY,UAAU,IAAI,CAAC,CAAC,SAAoB;GAClD;GAEA,yBAAyB,SAAS;IAChC,YAAY,UAAU,IAAI,CAAC,CAAC,SAAoB;GAClD;GAEA,uBAAuB,SAAS;IAC9B,YAAY,UAAU,IAAI,CAAC,CAAC,SAAoB;GAClD;GAEA,mBAAmB,SAAS;IAC1B,YAAY,UAAU,IAAI,CAAC,CAAC,SAAoB;GAClD;EACF;CACF;AACF,CACF"}
1
+ {"version":3,"file":"enforceAdapterImport.mjs","names":[],"sources":["../../../src/rules/enforceAdapterImport.ts"],"sourcesContent":["import { COMPAT_CALLERS } from '@intlayer/config/callers';\nimport { type AstNode, toAstNode } from '../utils/ast';\nimport { createRule } from '../utils/createRule';\n\ntype MessageIds = 'useAdapterImport';\n\nconst ADAPTER_SCOPE = '@intlayer/';\n\n/**\n * Original third-party library specifier → the `@intlayer/*` adapter that\n * replaces it, e.g. `next-intl` → `@intlayer/next-intl`.\n *\n * Derived from the shared registry rather than hard-coded: every compat\n * descriptor lists both its original specifiers and their adapter equivalents in\n * `importSources`, so a library added to `@intlayer/config/callers` is covered\n * here for free.\n *\n * Built from `COMPAT_CALLERS` only, never `ALL_CALLERS`. Intlayer's own packages\n * are not a migration pair: `BASE_CALLERS` lists `intlayer` alongside\n * `@intlayer/core` because `getIntlayer` is importable from both, and treating\n * the scoped one as an \"adapter\" would tell users to rewrite ordinary\n * `from 'intlayer'` imports.\n *\n * Sub-paths line up by position within a descriptor — `next-intl/server` and\n * `@intlayer/next-intl/server` are listed together — so the mapping is built by\n * matching each original against the adapter that ends with the same tail.\n */\nconst buildAdapterMap = (): Map<string, string> => {\n const adapterBySpecifier = new Map<string, string>();\n\n for (const descriptor of COMPAT_CALLERS) {\n const adapters = descriptor.importSources.filter((source) =>\n source.startsWith(ADAPTER_SCOPE)\n );\n\n if (adapters.length === 0) continue;\n\n const originals = descriptor.importSources.filter(\n (source) => !source.startsWith(ADAPTER_SCOPE)\n );\n\n for (const original of originals) {\n // `next-intl/server` → prefer `@intlayer/next-intl/server` over\n // `@intlayer/next-intl`, so match on the longest shared suffix.\n const adapter =\n adapters.find(\n (candidate) => candidate === `${ADAPTER_SCOPE}${original}`\n ) ??\n adapters.find((candidate) => candidate.endsWith(`/${original}`)) ??\n adapters[0];\n\n if (adapter && !adapterBySpecifier.has(original)) {\n adapterBySpecifier.set(original, adapter);\n }\n }\n }\n\n return adapterBySpecifier;\n};\n\nconst ADAPTER_BY_SPECIFIER = buildAdapterMap();\n\nexport const enforceAdapterImport = createRule<[], MessageIds>(\n 'enforce-adapter-import',\n {\n meta: {\n type: 'suggestion',\n fixable: 'code',\n docs: {\n description:\n 'Import compat-library APIs from their `@intlayer/*` adapter rather than the original package',\n },\n messages: {\n useAdapterImport:\n 'Import from `{{adapter}}` instead of `{{original}}`. The original specifier only resolves to Intlayer when the bundler alias is configured; the adapter always does.',\n },\n },\n\n create: (context) => {\n /** Report and rewrite a module specifier that has an adapter equivalent. */\n const checkSource = (node: AstNode | null | undefined): void => {\n if (!node) return;\n\n const original = node['value'];\n\n if (typeof original !== 'string') return;\n\n const adapter = ADAPTER_BY_SPECIFIER.get(original);\n\n if (!adapter) return;\n\n context.report({\n node: node as never,\n messageId: 'useAdapterImport',\n data: { adapter, original },\n fix: (fixer) => {\n const raw = (node['raw'] as string | undefined) ?? `'${original}'`;\n const quote = raw.startsWith('\"') ? '\"' : \"'\";\n\n return fixer.replaceText(\n node as never,\n `${quote}${adapter}${quote}`\n );\n },\n });\n };\n\n return {\n ImportDeclaration: (node) => {\n checkSource(toAstNode(node)['source'] as AstNode);\n },\n\n ExportNamedDeclaration: (node) => {\n checkSource(toAstNode(node)['source'] as AstNode);\n },\n\n ExportAllDeclaration: (node) => {\n checkSource(toAstNode(node)['source'] as AstNode);\n },\n\n ImportExpression: (node) => {\n checkSource(toAstNode(node)['source'] as AstNode);\n },\n };\n },\n }\n);\n"],"mappings":";;;;;AAMA,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;AAqBtB,MAAM,wBAA6C;CACjD,MAAM,qCAAqB,IAAI,IAAoB;CAEnD,KAAK,MAAM,cAAc,gBAAgB;EACvC,MAAM,WAAW,WAAW,cAAc,QAAQ,WAChD,OAAO,WAAW,aAAa,CACjC;EAEA,IAAI,SAAS,WAAW,GAAG;EAE3B,MAAM,YAAY,WAAW,cAAc,QACxC,WAAW,CAAC,OAAO,WAAW,aAAa,CAC9C;EAEA,KAAK,MAAM,YAAY,WAAW;GAGhC,MAAM,UACJ,SAAS,MACN,cAAc,cAAc,GAAG,gBAAgB,UAClD,KACA,SAAS,MAAM,cAAc,UAAU,SAAS,IAAI,UAAU,CAAC,KAC/D,SAAS;GAEX,IAAI,WAAW,CAAC,mBAAmB,IAAI,QAAQ,GAC7C,mBAAmB,IAAI,UAAU,OAAO;EAE5C;CACF;CAEA,OAAO;AACT;AAEA,MAAM,uBAAuB,gBAAgB;AAE7C,MAAa,uBAAuB,WAClC,0BACA;CACE,MAAM;EACJ,MAAM;EACN,SAAS;EACT,MAAM,EACJ,aACE,+FACJ;EACA,UAAU,EACR,kBACE,uKACJ;CACF;CAEA,SAAS,YAAY;;EAEnB,MAAM,eAAe,SAA2C;GAC9D,IAAI,CAAC,MAAM;GAEX,MAAM,WAAW,KAAK;GAEtB,IAAI,OAAO,aAAa,UAAU;GAElC,MAAM,UAAU,qBAAqB,IAAI,QAAQ;GAEjD,IAAI,CAAC,SAAS;GAEd,QAAQ,OAAO;IACP;IACN,WAAW;IACX,MAAM;KAAE;KAAS;IAAS;IAC1B,MAAM,UAAU;KAEd,MAAM,SADO,KAAK,UAAiC,IAAI,SAAS,GAC/C,CAAC,WAAW,IAAG,IAAI,OAAM;KAE1C,OAAO,MAAM,YACX,MACA,GAAG,QAAQ,UAAU,OACvB;IACF;GACF,CAAC;EACH;EAEA,OAAO;GACL,oBAAoB,SAAS;IAC3B,YAAY,UAAU,IAAI,CAAC,CAAC,SAAoB;GAClD;GAEA,yBAAyB,SAAS;IAChC,YAAY,UAAU,IAAI,CAAC,CAAC,SAAoB;GAClD;GAEA,uBAAuB,SAAS;IAC9B,YAAY,UAAU,IAAI,CAAC,CAAC,SAAoB;GAClD;GAEA,mBAAmB,SAAS;IAC1B,YAAY,UAAU,IAAI,CAAC,CAAC,SAAoB;GAClD;EACF;CACF;AACF,CACF"}
@@ -24,8 +24,8 @@ const staticDictionaryKey = createRule("static-dictionary-key", {
24
24
  docs: { description: "Require the dictionary key of an Intlayer content read to be a static string literal" },
25
25
  messages: {
26
26
  dynamicKey: "The dictionary key passed to `{{caller}}` must be a string literal. A computed key is invisible to the Intlayer compiler, which then skips the optimize pass and bundles every dictionary.",
27
- dynamicTemplateKey: "The dictionary key passed to `{{caller}}` interpolates a value. Use a string literal — an interpolated key is invisible to the Intlayer compiler, which then skips the optimize pass and bundles every dictionary.",
28
- dynamicJsxKey: "The `{{attribute}}` of `<{{caller}}>` must be a static string. A computed value is invisible to the Intlayer compiler, which then cannot resolve the dictionary at build time."
27
+ dynamicTemplateKey: "The dictionary key passed to `{{caller}}` interpolates a value. An interpolated key is invisible to the Intlayer compiler, which then skips the optimize pass and bundles every dictionary. Use a string literal, or variant (https://intlayer.org/doc/concept/variants) instead.",
28
+ dynamicJsxKey: "The `{{attribute}}` of `<{{caller}}>` must be a static string. A computed value is invisible to the Intlayer compiler, which then cannot resolve the dictionary at build time. Use a static string, or variant (https://intlayer.org/doc/concept/variants) instead"
29
29
  }
30
30
  },
31
31
  create: (context) => {
@@ -1 +1 @@
1
- {"version":3,"file":"staticDictionaryKey.mjs","names":[],"sources":["../../../src/rules/staticDictionaryKey.ts"],"sourcesContent":["import type { CallerDescriptor } from '@intlayer/config/callers';\nimport {\n type AstNode,\n getJsxAttributeName,\n getJsxAttributeValueNode,\n getJsxElementName,\n getStaticStringValue,\n toAstNode,\n} from '../utils/ast';\nimport {\n type CallerMatch,\n collectImportSources,\n getActiveDescriptors,\n matchCaller,\n matchJsxCaller,\n} from '../utils/callerMatcher';\nimport { createRule } from '../utils/createRule';\n\ntype MessageIds = 'dynamicKey' | 'dynamicTemplateKey' | 'dynamicJsxKey';\n\n/**\n * True when the call may legitimately omit its namespace — next-intl's\n * `useTranslations()` root scope derives the dictionary from each message id\n * instead, so an absent argument is not a violation.\n *\n * @param descriptor - The matched registry entry.\n */\nconst allowsMissingNamespace = (descriptor: CallerDescriptor): boolean =>\n descriptor.allowRootScope === true;\n\n/**\n * Distinguish an interpolated template from any other runtime expression, so\n * the report can name the actual problem.\n *\n * @param node - The namespace node.\n */\nconst isInterpolatedTemplate = (node: AstNode): boolean =>\n node.type === 'TemplateLiteral' &&\n ((node['expressions'] as unknown[] | undefined)?.length ?? 0) > 0;\n\nexport const staticDictionaryKey = createRule<[], MessageIds>(\n 'static-dictionary-key',\n {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Require the dictionary key of an Intlayer content read to be a static string literal',\n },\n messages: {\n dynamicKey:\n 'The dictionary key passed to `{{caller}}` must be a string literal. A computed key is invisible to the Intlayer compiler, which then skips the optimize pass and bundles every dictionary.',\n dynamicTemplateKey:\n 'The dictionary key passed to `{{caller}}` interpolates a value. Use a string literal — an interpolated key is invisible to the Intlayer compiler, which then skips the optimize pass and bundles every dictionary.',\n dynamicJsxKey:\n 'The `{{attribute}}` of `<{{caller}}>` must be a static string. A computed value is invisible to the Intlayer compiler, which then cannot resolve the dictionary at build time.',\n },\n },\n\n create: (context) => {\n let activeDescriptors = getActiveDescriptors(new Set());\n\n /** Report the namespace of a matched call when it is not static. */\n const reportDynamicNamespace = (match: CallerMatch): void => {\n const { descriptor, namespace } = match;\n\n if (!namespace) return;\n\n // A `fixed` namespace is a compile-time constant by construction.\n if (namespace.source.from === 'fixed') return;\n\n if (!namespace.node) {\n // Nothing was passed at all — only a problem for callers that require it.\n return;\n }\n\n if (namespace.value !== null) return;\n\n context.report({\n node: namespace.node as never,\n messageId: isInterpolatedTemplate(namespace.node)\n ? 'dynamicTemplateKey'\n : 'dynamicKey',\n data: { caller: descriptor.callerName },\n });\n };\n\n return {\n Program: (node) => {\n activeDescriptors = getActiveDescriptors(\n collectImportSources(toAstNode(node))\n );\n },\n\n CallExpression: (node) => {\n const match = matchCaller(toAstNode(node), activeDescriptors);\n\n if (!match) return;\n\n if (!match.namespace && allowsMissingNamespace(match.descriptor)) {\n return;\n }\n\n reportDynamicNamespace(match);\n },\n\n TaggedTemplateExpression: (node) => {\n const match = matchCaller(toAstNode(node), activeDescriptors);\n\n if (match) reportDynamicNamespace(match);\n },\n\n JSXOpeningElement: (node) => {\n const astNode = toAstNode(node);\n const descriptor = matchJsxCaller(\n getJsxElementName(astNode),\n activeDescriptors\n );\n\n if (!descriptor) return;\n\n const attributes =\n (astNode['attributes'] as AstNode[] | undefined) ?? [];\n\n const targetAttributes = [\n descriptor.jsxIdAttribute,\n descriptor.jsxNamespaceAttribute,\n ].filter((name): name is string => typeof name === 'string');\n\n for (const attribute of attributes) {\n const attributeName = getJsxAttributeName(attribute);\n\n if (!attributeName || !targetAttributes.includes(attributeName)) {\n continue;\n }\n\n const valueNode = getJsxAttributeValueNode(attribute);\n\n if (!valueNode) continue;\n\n if (getStaticStringValue(valueNode) !== null) continue;\n\n context.report({\n node: attribute as never,\n messageId: 'dynamicJsxKey',\n data: {\n attribute: attributeName,\n caller: descriptor.callerName,\n },\n });\n }\n },\n };\n },\n }\n);\n"],"mappings":";;;;;;;;;;;;AA2BA,MAAM,0BAA0B,eAC9B,WAAW,mBAAmB;;;;;;;AAQhC,MAAM,0BAA0B,SAC9B,KAAK,SAAS,sBACZ,KAAK,cAAc,EAA4B,UAAU,KAAK;AAElE,MAAa,sBAAsB,WACjC,yBACA;CACE,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,uFACJ;EACA,UAAU;GACR,YACE;GACF,oBACE;GACF,eACE;EACJ;CACF;CAEA,SAAS,YAAY;EACnB,IAAI,oBAAoB,qCAAqB,IAAI,IAAI,CAAC;;EAGtD,MAAM,0BAA0B,UAA6B;GAC3D,MAAM,EAAE,YAAY,cAAc;GAElC,IAAI,CAAC,WAAW;GAGhB,IAAI,UAAU,OAAO,SAAS,SAAS;GAEvC,IAAI,CAAC,UAAU,MAEb;GAGF,IAAI,UAAU,UAAU,MAAM;GAE9B,QAAQ,OAAO;IACb,MAAM,UAAU;IAChB,WAAW,uBAAuB,UAAU,IAAI,IAC5C,uBACA;IACJ,MAAM,EAAE,QAAQ,WAAW,WAAW;GACxC,CAAC;EACH;EAEA,OAAO;GACL,UAAU,SAAS;IACjB,oBAAoB,qBAClB,qBAAqB,UAAU,IAAI,CAAC,CACtC;GACF;GAEA,iBAAiB,SAAS;IACxB,MAAM,QAAQ,YAAY,UAAU,IAAI,GAAG,iBAAiB;IAE5D,IAAI,CAAC,OAAO;IAEZ,IAAI,CAAC,MAAM,aAAa,uBAAuB,MAAM,UAAU,GAC7D;IAGF,uBAAuB,KAAK;GAC9B;GAEA,2BAA2B,SAAS;IAClC,MAAM,QAAQ,YAAY,UAAU,IAAI,GAAG,iBAAiB;IAE5D,IAAI,OAAO,uBAAuB,KAAK;GACzC;GAEA,oBAAoB,SAAS;IAC3B,MAAM,UAAU,UAAU,IAAI;IAC9B,MAAM,aAAa,eACjB,kBAAkB,OAAO,GACzB,iBACF;IAEA,IAAI,CAAC,YAAY;IAEjB,MAAM,aACH,QAAQ,iBAA2C,CAAC;IAEvD,MAAM,mBAAmB,CACvB,WAAW,gBACX,WAAW,qBACb,CAAC,CAAC,QAAQ,SAAyB,OAAO,SAAS,QAAQ;IAE3D,KAAK,MAAM,aAAa,YAAY;KAClC,MAAM,gBAAgB,oBAAoB,SAAS;KAEnD,IAAI,CAAC,iBAAiB,CAAC,iBAAiB,SAAS,aAAa,GAC5D;KAGF,MAAM,YAAY,yBAAyB,SAAS;KAEpD,IAAI,CAAC,WAAW;KAEhB,IAAI,qBAAqB,SAAS,MAAM,MAAM;KAE9C,QAAQ,OAAO;MACb,MAAM;MACN,WAAW;MACX,MAAM;OACJ,WAAW;OACX,QAAQ,WAAW;MACrB;KACF,CAAC;IACH;GACF;EACF;CACF;AACF,CACF"}
1
+ {"version":3,"file":"staticDictionaryKey.mjs","names":[],"sources":["../../../src/rules/staticDictionaryKey.ts"],"sourcesContent":["import type { CallerDescriptor } from '@intlayer/config/callers';\nimport {\n type AstNode,\n getJsxAttributeName,\n getJsxAttributeValueNode,\n getJsxElementName,\n getStaticStringValue,\n toAstNode,\n} from '../utils/ast';\nimport {\n type CallerMatch,\n collectImportSources,\n getActiveDescriptors,\n matchCaller,\n matchJsxCaller,\n} from '../utils/callerMatcher';\nimport { createRule } from '../utils/createRule';\n\ntype MessageIds = 'dynamicKey' | 'dynamicTemplateKey' | 'dynamicJsxKey';\n\n/**\n * True when the call may legitimately omit its namespace — next-intl's\n * `useTranslations()` root scope derives the dictionary from each message id\n * instead, so an absent argument is not a violation.\n *\n * @param descriptor - The matched registry entry.\n */\nconst allowsMissingNamespace = (descriptor: CallerDescriptor): boolean =>\n descriptor.allowRootScope === true;\n\n/**\n * Distinguish an interpolated template from any other runtime expression, so\n * the report can name the actual problem.\n *\n * @param node - The namespace node.\n */\nconst isInterpolatedTemplate = (node: AstNode): boolean =>\n node.type === 'TemplateLiteral' &&\n ((node['expressions'] as unknown[] | undefined)?.length ?? 0) > 0;\n\nexport const staticDictionaryKey = createRule<[], MessageIds>(\n 'static-dictionary-key',\n {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Require the dictionary key of an Intlayer content read to be a static string literal',\n },\n messages: {\n dynamicKey:\n 'The dictionary key passed to `{{caller}}` must be a string literal. A computed key is invisible to the Intlayer compiler, which then skips the optimize pass and bundles every dictionary.',\n dynamicTemplateKey:\n 'The dictionary key passed to `{{caller}}` interpolates a value. An interpolated key is invisible to the Intlayer compiler, which then skips the optimize pass and bundles every dictionary. Use a string literal, or variant (https://intlayer.org/doc/concept/variants) instead.',\n dynamicJsxKey:\n 'The `{{attribute}}` of `<{{caller}}>` must be a static string. A computed value is invisible to the Intlayer compiler, which then cannot resolve the dictionary at build time. Use a static string, or variant (https://intlayer.org/doc/concept/variants) instead',\n },\n },\n\n create: (context) => {\n let activeDescriptors = getActiveDescriptors(new Set());\n\n /** Report the namespace of a matched call when it is not static. */\n const reportDynamicNamespace = (match: CallerMatch): void => {\n const { descriptor, namespace } = match;\n\n if (!namespace) return;\n\n // A `fixed` namespace is a compile-time constant by construction.\n if (namespace.source.from === 'fixed') return;\n\n if (!namespace.node) {\n // Nothing was passed at all — only a problem for callers that require it.\n return;\n }\n\n if (namespace.value !== null) return;\n\n context.report({\n node: namespace.node as never,\n messageId: isInterpolatedTemplate(namespace.node)\n ? 'dynamicTemplateKey'\n : 'dynamicKey',\n data: { caller: descriptor.callerName },\n });\n };\n\n return {\n Program: (node) => {\n activeDescriptors = getActiveDescriptors(\n collectImportSources(toAstNode(node))\n );\n },\n\n CallExpression: (node) => {\n const match = matchCaller(toAstNode(node), activeDescriptors);\n\n if (!match) return;\n\n if (!match.namespace && allowsMissingNamespace(match.descriptor)) {\n return;\n }\n\n reportDynamicNamespace(match);\n },\n\n TaggedTemplateExpression: (node) => {\n const match = matchCaller(toAstNode(node), activeDescriptors);\n\n if (match) reportDynamicNamespace(match);\n },\n\n JSXOpeningElement: (node) => {\n const astNode = toAstNode(node);\n const descriptor = matchJsxCaller(\n getJsxElementName(astNode),\n activeDescriptors\n );\n\n if (!descriptor) return;\n\n const attributes =\n (astNode['attributes'] as AstNode[] | undefined) ?? [];\n\n const targetAttributes = [\n descriptor.jsxIdAttribute,\n descriptor.jsxNamespaceAttribute,\n ].filter((name): name is string => typeof name === 'string');\n\n for (const attribute of attributes) {\n const attributeName = getJsxAttributeName(attribute);\n\n if (!attributeName || !targetAttributes.includes(attributeName)) {\n continue;\n }\n\n const valueNode = getJsxAttributeValueNode(attribute);\n\n if (!valueNode) continue;\n\n if (getStaticStringValue(valueNode) !== null) continue;\n\n context.report({\n node: attribute as never,\n messageId: 'dynamicJsxKey',\n data: {\n attribute: attributeName,\n caller: descriptor.callerName,\n },\n });\n }\n },\n };\n },\n }\n);\n"],"mappings":";;;;;;;;;;;;AA2BA,MAAM,0BAA0B,eAC9B,WAAW,mBAAmB;;;;;;;AAQhC,MAAM,0BAA0B,SAC9B,KAAK,SAAS,sBACZ,KAAK,cAAc,EAA4B,UAAU,KAAK;AAElE,MAAa,sBAAsB,WACjC,yBACA;CACE,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,uFACJ;EACA,UAAU;GACR,YACE;GACF,oBACE;GACF,eACE;EACJ;CACF;CAEA,SAAS,YAAY;EACnB,IAAI,oBAAoB,qCAAqB,IAAI,IAAI,CAAC;;EAGtD,MAAM,0BAA0B,UAA6B;GAC3D,MAAM,EAAE,YAAY,cAAc;GAElC,IAAI,CAAC,WAAW;GAGhB,IAAI,UAAU,OAAO,SAAS,SAAS;GAEvC,IAAI,CAAC,UAAU,MAEb;GAGF,IAAI,UAAU,UAAU,MAAM;GAE9B,QAAQ,OAAO;IACb,MAAM,UAAU;IAChB,WAAW,uBAAuB,UAAU,IAAI,IAC5C,uBACA;IACJ,MAAM,EAAE,QAAQ,WAAW,WAAW;GACxC,CAAC;EACH;EAEA,OAAO;GACL,UAAU,SAAS;IACjB,oBAAoB,qBAClB,qBAAqB,UAAU,IAAI,CAAC,CACtC;GACF;GAEA,iBAAiB,SAAS;IACxB,MAAM,QAAQ,YAAY,UAAU,IAAI,GAAG,iBAAiB;IAE5D,IAAI,CAAC,OAAO;IAEZ,IAAI,CAAC,MAAM,aAAa,uBAAuB,MAAM,UAAU,GAC7D;IAGF,uBAAuB,KAAK;GAC9B;GAEA,2BAA2B,SAAS;IAClC,MAAM,QAAQ,YAAY,UAAU,IAAI,GAAG,iBAAiB;IAE5D,IAAI,OAAO,uBAAuB,KAAK;GACzC;GAEA,oBAAoB,SAAS;IAC3B,MAAM,UAAU,UAAU,IAAI;IAC9B,MAAM,aAAa,eACjB,kBAAkB,OAAO,GACzB,iBACF;IAEA,IAAI,CAAC,YAAY;IAEjB,MAAM,aACH,QAAQ,iBAA2C,CAAC;IAEvD,MAAM,mBAAmB,CACvB,WAAW,gBACX,WAAW,qBACb,CAAC,CAAC,QAAQ,SAAyB,OAAO,SAAS,QAAQ;IAE3D,KAAK,MAAM,aAAa,YAAY;KAClC,MAAM,gBAAgB,oBAAoB,SAAS;KAEnD,IAAI,CAAC,iBAAiB,CAAC,iBAAiB,SAAS,aAAa,GAC5D;KAGF,MAAM,YAAY,yBAAyB,SAAS;KAEpD,IAAI,CAAC,WAAW;KAEhB,IAAI,qBAAqB,SAAS,MAAM,MAAM;KAE9C,QAAQ,OAAO;MACb,MAAM;MACN,WAAW;MACX,MAAM;OACJ,WAAW;OACX,QAAQ,WAAW;MACrB;KACF,CAAC;IACH;GACF;EACF;CACF;AACF,CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"enforceAdapterImport.d.ts","names":[],"sources":["../../../src/rules/enforceAdapterImport.ts"],"mappings":";;cAuDa,sBAAA"}
1
+ {"version":3,"file":"enforceAdapterImport.d.ts","names":[],"sources":["../../../src/rules/enforceAdapterImport.ts"],"mappings":";;cA8Da,sBAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-intlayer",
3
- "version": "9.3.2",
3
+ "version": "9.3.3",
4
4
  "private": false,
5
5
  "description": "ESLint (and oxlint) plugin for Intlayer. Catches untranslated strings and the dynamic call shapes the Intlayer compiler cannot optimize.",
6
6
  "keywords": [
@@ -76,8 +76,8 @@
76
76
  "typecheck": "tsc --noEmit --project tsconfig.types.json"
77
77
  },
78
78
  "dependencies": {
79
- "@intlayer/config": "9.3.2",
80
- "@intlayer/lsp": "9.3.2",
79
+ "@intlayer/config": "9.3.3",
80
+ "@intlayer/lsp": "9.3.3",
81
81
  "fast-glob": "3.3.3"
82
82
  },
83
83
  "devDependencies": {
@@ -95,7 +95,7 @@
95
95
  "tsdown": "0.22.14",
96
96
  "typescript": "6.0.3",
97
97
  "typescript-eslint": "8.67.0",
98
- "vitest": "4.1.10",
98
+ "vitest": "4.1.11",
99
99
  "vue-eslint-parser": "10.4.1"
100
100
  },
101
101
  "peerDependencies": {