Skip to content

Conversation

@aground5
Copy link

@aground5 aground5 commented Dec 24, 2025

PR: Implement Next.js AST Analyzer & Fix as const Support

📝 Description

This PR introduces a robust AST-based analyzer for the next-intl framework in i18n-ally. It significantly improves the detection of translation keys and namespaces by supporting constant propagation, variable shadowing, and indirect usage (e.g., t(key) where key is a variable).
Specifically, this PR addresses a critical issue where TypeScript assertions (like as const) prevented the analyzer from correctly identifying static values, leading to missed keys.

🚀 Changes

  • New AST Analyzer (src/analyzers/ast-analyzer.ts):
    • Implemented analyzeUseTranslations to detect useTranslations calls and resolve namespaces via scope analysis.
    • Implemented analyzeTranslationUsages to track t() calls and resolve keys using constant propagation (foldConstant).
    • Added support for finding keys in:
      • String literals: t('key')
      • Template literals: [t(](file:///Users/k2zoo/Documents/coding/i18n-ally/test/unit/analyzers/ast-analyzer.test.ts#220-226)key`)`
      • Variables via binding lookup: const k = 'key'; t(k)
      • Array/Object mapping: ITEMS.map(i => t(i.id))
  • Fix for TypeScript Assertions:
    • Updated foldConstant to recursively unwrap TypeScript expressions (TSAsExpression, TSTypeAssertion, etc.).
    • This ensures useTranslations('ns' as const) and t('key' as const) are now correctly resolved.
  • Framework Integration:
    • Updated src/frameworks/next-intl.ts to utilize the new AST analyzer for detection and scope resolution.
  • Tests:
    • Added comprehensive unit tests in test/unit/analyzers/ast-analyzer.test.ts.
    • Added specific reproduction tests for as const issues in test/unit/analyzers/repro_issue.test.ts.

✅ Verification

  • Unit Tests: All 74 tests in ast-analyzer.test.ts passed.
  • Reproduction Tests: Verified that as const usage is correctly handled.
  • Build: Project builds successfully (npm run build).
  • Packaging: VSIX package generated successfully (npx vsce package).

🔗 Related Issues

  • Fixes issue where next-intl keys were not detected when using variables or as const.

Commit: feat: implement Next.js AST analyzer and fix 'as const' support

Summary by CodeRabbit

  • New Features

    • Added ability to export usage reports as Markdown files with summary statistics, active keys, idle keys, and missing keys sections.
  • Localization

    • Added English and Korean translations for export report functionality.
  • Improvements

    • Enhanced translation key detection for Next.js intl framework projects.

✏️ Tip: You can customize this high-level summary in your review settings.

@aground5 aground5 requested a review from kibertoad as a code owner December 24, 2025 08:38
@coderabbitai
Copy link

coderabbitai bot commented Dec 24, 2025

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key(s) in object: 'tools'
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

This PR introduces an export usage report feature by adding an AST-based translation key analyzer to detect translation calls, implementing a new export command that generates Markdown reports, and extending the framework detection system to support AST-assisted key analysis.

Changes

Cohort / File(s) Summary
Localization Entries
locales/en.json, locales/ko.json
Added three new locale keys for export functionality: command.export_usage_report, command.export_no_report, and command.export_success with English and Korean translations.
VS Code Extension Manifest
package.json
Registered new command i18n-ally.export-usage-report in contributes.commands; added view-title UI entry with group navigation@2 and condition requiring i18n-ally-has-report; reorganized existing i18n-ally.refresh-usage to group navigation@1.
Command Module
src/commands/commands.ts, src/commands/exportUsageReport.ts, src/commands/index.ts
Added export_usage_report enum member; implemented new command handler that calls Analyst.analyzeUsage(), generates Markdown report (Summary, Active/Idle/Missing Keys), prompts save dialog, and writes file to disk with completion notification; integrated into command pipeline.
AST Analysis Framework
src/analyzers/ast-analyzer.ts
New module providing Babel-based static analysis for translation calls; exports interfaces UseTranslationsAnalysis and AnalyzedKey; implements functions analyzeUseTranslations() (identifies variable-namespace mappings from useTranslations/getTranslations calls), analyzeTranslationUsages() (resolves t(...) usages with namespace-aware keys), foldConstant() (constant-folding resolver for literals, identifiers, and simple expressions), and parseCode() (Babel parser wrapper).
Framework Detection System
src/core/KeyDetector.ts, src/frameworks/base.ts, src/frameworks/next-intl.ts
Added detectKeys(document) method to Framework base class; updated KeyDetector to call framework.detectKeys() early before cache/regex fallback; implemented detectKeys() in NextIntlFramework using AST analysis with namespace resolution; added createImporter() helper for path resolution; updated regex patterns to account for dot-qualified methods (.rich, .markup, .raw).
Unit Tests
test/unit/analyzers/ast-analyzer.test.ts, test/unit/analyzers/repro_issue.test.ts
Added comprehensive test suite covering analyzeUseTranslations and analyzeTranslationUsages with scenarios for static/dynamic namespaces, template literals, constant propagation, array/object maps, as const patterns, and namespace shadowing; added reproduction tests for edge cases.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant VSCode as VSCode Extension
    participant Analyst
    participant Generator as Markdown Generator
    participant Dialog as Save Dialog
    participant FileSystem as File System
    participant Notif as Notification

    User->>VSCode: Invoke export-usage-report command
    VSCode->>Analyst: analyzeUsage(true)
    Analyst-->>VSCode: UsageReport
    
    alt Report is empty
        VSCode->>Notif: Show warning (i18n: export_no_report)
        Notif-->>User: Display message
    else Report available
        rect rgb(240, 248, 255)
            VSCode->>Generator: generateMarkdown(report)
            Generator-->>VSCode: Markdown string
        end
        
        VSCode->>Dialog: showSaveDialog(defaultName: i18n-usage-report.md)
        Dialog-->>User: Save dialog
        User->>Dialog: Choose destination
        Dialog-->>VSCode: File URI
        
        VSCode->>FileSystem: workspace.fs.writeFile(uri, markdown)
        FileSystem-->>VSCode: Success
        
        VSCode->>Notif: Show info with Open File option
        Notif-->>User: Display success message
        
        alt User clicks Open File
            User->>VSCode: Open File action
            VSCode->>VSCode: vscode.open(uri)
            VSCode-->>User: Open in editor
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: implementing an AST analyzer for Next.js/next-intl and fixing 'as const' support, which aligns with the core objectives of the PR.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (7)
src/core/KeyDetector.ts (1)

103-109: Consider caching AST-detected keys for performance.

The framework-specific detection path returns early before the cache lookup, meaning every call to getKeys for files handled by AST-based frameworks will re-parse the AST. For large files or frequent calls (e.g., during typing), this could impact performance.

Consider caching the AST-detected results similarly to how regex-based results are cached:

       // Try framework-specific detection first (e.g. AST based)
       for (const framework of Global.enabledFrameworks) {
         const detected = framework.detectKeys?.(document)
-        if (detected)
+        if (detected) {
+          this._get_keys_cache[filepath] = detected
           return detected
+        }
       }

This would allow the cache invalidation on document change (line 86-92) to work for AST-detected files as well.

test/unit/analyzers/repro_issue.test.ts (1)

27-36: Consider adding a negative test case for array map resolution.

The array map test assumes ITEMS is always resolvable. Consider adding a test case where the array source cannot be statically resolved (e.g., a function parameter or external import) to verify graceful handling of unresolvable dynamic values.

test/unit/analyzers/ast-analyzer.test.ts (1)

204-214: Minor style: missing blank line before test case.

For consistency with other test cases in this file, consider adding a blank line before this test.

src/frameworks/next-intl.ts (2)

82-127: Synchronous file operations may block the extension host.

The createImporter function uses synchronous fs.existsSync and File.readSync calls. In a VS Code extension, synchronous I/O on the main thread can cause UI freezes, especially when resolving multiple imports in large files.

Consider making this async or using a caching strategy to minimize blocking. For now, this is acceptable if the number of imports per file is typically small.


100-107: Redundant path resolution when no path exists.

When none of the possiblePaths exist, line 107 still sets resolvedPath = possiblePaths[0], which will then fail in the extension loop below. This is intentional fallback behavior but could be clarified with a comment.

🔎 Suggested clarification
-           // Fallback to simple join if not found, to try extensions below
-           if (!resolvedPath) resolvedPath = possiblePaths[0]
+           // Fallback: try first path with various extensions below
+           // This allows .ts/.tsx/.js extensions to be tested even if base path doesn't exist
+           if (!resolvedPath) resolvedPath = possiblePaths[0]
src/commands/exportUsageReport.ts (1)

92-99: Default URI may not resolve to a meaningful location.

Uri.file('i18n-usage-report.md') creates a URI relative to the process working directory, which may not be the workspace folder. Consider using the workspace folder as the base.

🔎 Proposed fix
+        const workspaceFolder = workspace.workspaceFolders?.[0]?.uri
         const uri = await window.showSaveDialog({
-          defaultUri: Uri.file('i18n-usage-report.md'),
+          defaultUri: workspaceFolder 
+            ? Uri.joinPath(workspaceFolder, 'i18n-usage-report.md')
+            : Uri.file('i18n-usage-report.md'),
           filters: {
             Markdown: ['md'],
             'All Files': ['*'],
           },
           title: 'Export Usage Report',
         })
src/analyzers/ast-analyzer.ts (1)

406-408: fileName is hardcoded when it could be derived from context.

Unlike analyzeUseTranslations which accepts filePath, analyzeTranslationUsages doesn't accept a file path parameter but uses fileName = 'unknown' on line 408. Consider adding an optional filePath parameter for consistency.

📜 Review details

Configuration used: Repository: lokalise/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4c504c9 and 550bafe.

📒 Files selected for processing (12)
  • locales/en.json
  • locales/ko.json
  • package.json
  • src/analyzers/ast-analyzer.ts
  • src/commands/commands.ts
  • src/commands/exportUsageReport.ts
  • src/commands/index.ts
  • src/core/KeyDetector.ts
  • src/frameworks/base.ts
  • src/frameworks/next-intl.ts
  • test/unit/analyzers/ast-analyzer.test.ts
  • test/unit/analyzers/repro_issue.test.ts
🧰 Additional context used
🧬 Code graph analysis (5)
src/frameworks/base.ts (1)
src/core/types.ts (1)
  • KeyInDocument (113-118)
test/unit/analyzers/repro_issue.test.ts (1)
src/analyzers/ast-analyzer.ts (2)
  • analyzeUseTranslations (324-392)
  • analyzeTranslationUsages (398-563)
src/core/KeyDetector.ts (1)
src/core/Global.ts (1)
  • Global (24-464)
test/unit/analyzers/ast-analyzer.test.ts (1)
src/analyzers/ast-analyzer.ts (2)
  • analyzeUseTranslations (324-392)
  • analyzeTranslationUsages (398-563)
src/commands/exportUsageReport.ts (3)
src/core/types.ts (1)
  • UsageReport (132-136)
src/modules.ts (1)
  • ExtensionModule (3-5)
src/core/Analyst.ts (1)
  • Analyst (12-178)
🔇 Additional comments (17)
locales/ko.json (1)

29-31: LGTM!

The new Korean localization entries for the export usage report feature are correctly structured and follow the existing naming conventions. The placeholder {0} is properly used for the file path in the success message.

locales/en.json (1)

29-31: LGTM!

The new English localization entries are clear, grammatically correct, and consistent with the existing command key patterns.

src/commands/commands.ts (1)

48-48: LGTM!

The new enum member follows the established naming conventions and matches the command string registered in package.json.

src/commands/index.ts (1)

15-15: LGTM!

The new exportUsageReport module is correctly imported and integrated into the command pipeline following the established pattern. The placement after refreshUsageReport is logical since users would typically refresh before exporting.

Also applies to: 28-28

src/frameworks/base.ts (1)

65-70: LGTM!

The new detectKeys method is a well-designed extension point that follows the existing pattern of optional framework-level hooks (similar to detectHardStrings and getScopeRange). The default undefined return allows frameworks to opt-in to AST-based detection while maintaining backward compatibility.

test/unit/analyzers/repro_issue.test.ts (1)

1-4: LGTM!

The reproduction tests provide good coverage for edge cases including template literals, binary expressions, array mapping, and as const assertions. The test structure is clean and each test case is focused on a specific scenario.

package.json (1)

622-626: The i18n-ally-has-report context is correctly set in src/views/providers/UsageReportProvider.ts (line 25). The context is enabled when a usage report contains active or idle entries, ensuring the export button appears only when data is available. Implementation is sound.

test/unit/analyzers/ast-analyzer.test.ts (3)

1-14: LGTM! Well-structured test setup.

The test file properly imports the analyzer functions and uses chai for assertions. The first test case validates the basic string literal namespace detection correctly.


246-263: Good test for scope-aware namespace resolution.

This test validates that the analyzer correctly respects lexical scoping when the same variable name t is used in different functions with different namespaces. The comment on line 257 accurately describes the fallback behavior.


279-299: Excellent coverage for variable shadowing.

This test properly validates that inner-scope variable declarations shadow outer-scope ones during constant propagation. The use of line numbers to identify results is a pragmatic approach.

src/frameworks/next-intl.ts (2)

129-158: LGTM! Clean integration of AST analysis for key detection.

The detectKeys method properly:

  1. Checks language ID compatibility
  2. Creates an importer for cross-file resolution
  3. Builds variable-to-namespace mappings from useTranslations calls
  4. Analyzes translation usages with the namespace map

The TODO on line 145 for handling dynamic namespaces is noted and appropriate for a future enhancement.


209-223: Regex fallback provides graceful degradation for namespace detection.

The fallback regex /namespace:\s*['"]([^'"`]+)['"`]/gonly matches object-property-style namespace declarations like{ namespace: 'value' }. However, this is acceptable because the primary AST analyzer handles the broader range of patterns (string literal arguments to useTranslations(), template literals, and variable resolution), and the fallback is invoked only when AST analysis yields no results. For next-intl's typical usage (useTranslations('name')orgetTranslations({ namespace: 'name' })`), the AST analyzer already captures the primary cases.

src/commands/exportUsageReport.ts (1)

7-74: Well-structured Markdown generation.

The generateMarkdown function produces a clean, readable report with:

  • Timestamped header
  • Summary table with counts
  • Expandable idle keys section (good UX for large lists)
  • Clear categorization of active, idle, and missing keys
src/analyzers/ast-analyzer.ts (4)

1-4: LGTM! Appropriate use of @ts-ignore.

The @ts-ignore for @babel/traverse is acceptable since the package lacks complete TypeScript definitions. Consider adding a more descriptive comment explaining why.


66-266: Well-implemented constant folding with appropriate limits.

The foldConstant function handles:

  • TS type assertions (as const, type assertions, non-null assertions)
  • Literals and template literals
  • Arrays (string and object arrays)
  • Identifier resolution via scope bindings
  • Cross-file imports via the importer callback
  • Binary concatenation
  • Recursion depth limit (5) to prevent infinite loops

The implementation is robust for the stated use cases.


324-392: LGTM! Clean implementation of analyzeUseTranslations.

The function properly:

  • Parses code with error handling
  • Traverses VariableDeclarator nodes
  • Handles both direct calls and await expressions
  • Uses scope-aware namespace resolution
  • Returns structured analysis results with location info

462-464: No issue found - early return is intentional design.

The code correctly handles empty namespaces (line 198–202 test confirms new Map([['t', '']]) processes keys as 'title' without prefix). The early return requires translation functions to be registered in varNamespaceMap or resolved via scope analysis—this is defensive design to skip untracked translation calls, not a bug. Both intentionally empty namespaces and different sources are handled correctly through the designed mechanism: calling code must pre-populate varNamespaceMap with known translation variables.

Comment on lines +120 to +123
else if (!res.isDynamic && typeof res.value === 'object' && res.value !== null && !Array.isArray(res.value)) {
// It's likely an object setup from ObjectExpression below, but foldConstant currently doesn't return single object.
// Let's handle ObjectExpression explicitly here for element structure
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Empty conditional branch has no effect.

Lines 120-123 check for a resolved object value but don't do anything with it. The logic falls through to the ObjectExpression handling below, but this branch appears incomplete.

🔎 Suggested fix - remove dead code
       if (!res.isDynamic && typeof res.value === 'string') {
           if (type === 'unknown') type = 'string'
           if (type === 'string') {
             stringElements.push(res.value)
           } else {
             type = 'mixed'
             break
           }
       }
-      else if (!res.isDynamic && typeof res.value === 'object' && res.value !== null && !Array.isArray(res.value)) {
-           // It's likely an object setup from ObjectExpression below, but foldConstant currently doesn't return single object.
-           // Let's handle ObjectExpression explicitly here for element structure
-      }
       // Direct ObjectExpression handling to match previous logic
       else if (el.type === 'ObjectExpression') {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
else if (!res.isDynamic && typeof res.value === 'object' && res.value !== null && !Array.isArray(res.value)) {
// It's likely an object setup from ObjectExpression below, but foldConstant currently doesn't return single object.
// Let's handle ObjectExpression explicitly here for element structure
}
// Direct ObjectExpression handling to match previous logic
else if (el.type === 'ObjectExpression') {
🤖 Prompt for AI Agents
In src/analyzers/ast-analyzer.ts around lines 120 to 123, the else-if branch
that checks for a resolved non-array object (else if (!res.isDynamic && typeof
res.value === 'object' && res.value !== null && !Array.isArray(res.value)) { ...
}) is empty and has no effect; remove that entire conditional branch to avoid
dead code (or if you intended to handle folded single-object constants,
implement the explicit ObjectExpression handling there instead of leaving it
empty) so control falls through to the existing ObjectExpression logic below.

Comment on lines +129 to +141
let isSimpleObject = true
for (const prop of el.properties) {
if (prop.type === 'ObjectProperty' && prop.key.type === 'Identifier') {
const valRes = foldConstant(prop.value, scope, importer, depth + 1)
if (!valRes.isDynamic && typeof valRes.value === 'string') {
obj[prop.key.name] = valRes.value
}
}
// Ignore complex properties for now
}
if (isSimpleObject) {
objectElements.push(obj)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

isSimpleObject is always true and has no effect.

The variable isSimpleObject is initialized to true on line 129 but never set to false, making the condition on line 139 always true. Either remove it or implement the intended logic to detect complex properties.

🔎 Suggested fix
            const obj: Record<string, string> = {}
-           let isSimpleObject = true
            for (const prop of el.properties) {
               if (prop.type === 'ObjectProperty' && prop.key.type === 'Identifier') {
                  const valRes = foldConstant(prop.value, scope, importer, depth + 1)
                  if (!valRes.isDynamic && typeof valRes.value === 'string') {
                      obj[prop.key.name] = valRes.value
                  }
               }
               // Ignore complex properties for now
            }
-           if (isSimpleObject) {
-               objectElements.push(obj)
-           }
+           objectElements.push(obj)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let isSimpleObject = true
for (const prop of el.properties) {
if (prop.type === 'ObjectProperty' && prop.key.type === 'Identifier') {
const valRes = foldConstant(prop.value, scope, importer, depth + 1)
if (!valRes.isDynamic && typeof valRes.value === 'string') {
obj[prop.key.name] = valRes.value
}
}
// Ignore complex properties for now
}
if (isSimpleObject) {
objectElements.push(obj)
}
const obj: Record<string, string> = {}
for (const prop of el.properties) {
if (prop.type === 'ObjectProperty' && prop.key.type === 'Identifier') {
const valRes = foldConstant(prop.value, scope, importer, depth + 1)
if (!valRes.isDynamic && typeof valRes.value === 'string') {
obj[prop.key.name] = valRes.value
}
}
// Ignore complex properties for now
}
objectElements.push(obj)

Comment on lines +168 to +206
if (binding.kind === 'module') {
if (binding.path.isImportSpecifier()) {
const importedName = binding.path.node.imported.name
const importDecl = binding.path.parentPath.node
const source = importDecl.source.value

if (importer) {
const code = importer(source)
if (code) {
const subAst = parseCode(code)
if (subAst) {
let importedValue: ResolvedValue = { value: null, isDynamic: true }
// Traverse the imported file to find the export
traverse(subAst, {
ExportNamedDeclaration(path: any) {
const declaration = path.node.declaration
if (declaration?.type === 'VariableDeclaration') {
for (const decl of declaration.declarations) {
if (decl.id.name === importedName) {
// Found valid export, resolve its value in its own scope (global in that file)
// We create a dummy scope or just resolve based on init node since top level
// For simplicity, we call foldConstant with a dummy scope or standard AST traversal for that node
// Note: Cross-file scope resolution is hard. We'll reuse the logic by passing the sub-file AST path
// But we don't have scope path easily.
// Simplified: Just check the init node directly if it's simple literal.
// Scope usage in imported file is not supported yet (except simple constants)
importedValue = foldConstant(decl.init, { getBinding: () => null }, undefined, depth + 1)
}
}
}
}
})
return importedValue
}
}
}
}
return { value: null, isDynamic: true }
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Import resolution assumes named imports only.

Line 170 accesses binding.path.node.imported.name, which works for named imports like import { CATEGORIES } from './constants' but will fail for:

  • Default imports: import CATEGORIES from './constants'
  • Namespace imports: import * as constants from './constants'
🔎 Suggested defensive check
     if (binding.kind === 'module') {
-       if (binding.path.isImportSpecifier()) {
-           const importedName = binding.path.node.imported.name
+       if (binding.path.isImportSpecifier() && binding.path.node.imported?.type === 'Identifier') {
+           const importedName = binding.path.node.imported.name
            // ... rest of logic
+       }
+       // TODO: Handle default imports and namespace imports if needed
        return { value: null, isDynamic: true }
     }
🤖 Prompt for AI Agents
In src/analyzers/ast-analyzer.ts around lines 168 to 206, the code assumes
binding.path.node.imported exists (named import) and will throw for default or
namespace imports; add defensive checks and handle the three import specifier
types: use path.isImportSpecifier() before accessing node.imported and handle
ImportDefaultSpecifier by looking for ExportDefaultDeclaration or a named export
mapped to default in the imported AST, and handle ImportNamespaceSpecifier by
not trying to resolve a single identifier (return dynamic or attempt to resolve
property accesses later). Ensure all accesses to node.imported/node.local are
guarded (null checks) and fall back to returning {value: null, isDynamic: true}
when the import form or export cannot be resolved.

Comment on lines +66 to +69
for (const usage of report.missing) {
const files = [...new Set(usage.occurrences.map(o => o.filepath.split('/').pop()))].join(', ')
lines.push(`| \`${usage.keypath}\` | ${files} |`)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Path splitting fails on Windows.

Using filepath.split('/').pop() won't correctly extract filenames on Windows where paths use backslashes. Use path.basename() or a cross-platform approach.

🔎 Proposed fix

First, add the import at the top of the file:

import * as path from 'path'

Then update the path extraction:

-      const files = [...new Set(usage.occurrences.map(o => o.filepath.split('/').pop()))].join(', ')
+      const files = [...new Set(usage.occurrences.map(o => path.basename(o.filepath)))].join(', ')
🤖 Prompt for AI Agents
In src/commands/exportUsageReport.ts around lines 66 to 69, the code uses
filepath.split('/').pop() which fails on Windows; import Node's path module at
the top of the file (e.g., import * as path from 'path') and replace the
split/pop expression with path.basename(usage.occurrences.map(...)? No — use
path.basename(o.filepath) when mapping: build the Set from
usage.occurrences.map(o => path.basename(o.filepath)) and join as before so
filenames are extracted in a cross-platform way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant