* Check if an import is external (npm package, etc.) * * `context` is consulted for project-defined path aliases * (tsconfig/jsconfig `paths`). Without that check, custom prefixes * like `@components/*` would fail the bare-specifier heuristic and * be classified as external before alias resolut
( importPath: string, language: Language, context?: ResolutionContext )
| 122 | * be classified as external before alias resolution can run. |
| 123 | */ |
| 124 | function isExternalImport( |
| 125 | importPath: string, |
| 126 | language: Language, |
| 127 | context?: ResolutionContext |
| 128 | ): boolean { |
| 129 | // Relative imports are not external |
| 130 | if (importPath.startsWith('.')) { |
| 131 | return false; |
| 132 | } |
| 133 | |
| 134 | // Workspace-member imports (`@scope/ui`, `@scope/ui/widgets`) are LOCAL to |
| 135 | // a monorepo even though they look like bare npm specifiers. Consult the |
| 136 | // workspace map first so they aren't misclassified as external (#629). The |
| 137 | // map is null for single-package repos, so this is a no-op there. |
| 138 | const workspaces = context?.getWorkspacePackages?.(); |
| 139 | if (workspaces && resolveWorkspaceImport(importPath, workspaces)) { |
| 140 | return false; |
| 141 | } |
| 142 | |
| 143 | // Common external patterns |
| 144 | if (language === 'typescript' || language === 'javascript' || language === 'tsx' || language === 'jsx') { |
| 145 | // Node built-ins |
| 146 | if (['fs', 'path', 'os', 'crypto', 'http', 'https', 'url', 'util', 'events', 'stream', 'child_process', 'buffer'].includes(importPath)) { |
| 147 | return true; |
| 148 | } |
| 149 | // Project-defined alias prefix? Treat as local. |
| 150 | const aliases = context?.getProjectAliases?.(); |
| 151 | if (aliases) { |
| 152 | for (const pat of aliases.patterns) { |
| 153 | if (importPath.startsWith(pat.prefix)) return false; |
| 154 | } |
| 155 | } |
| 156 | // Scoped packages or bare specifiers that don't start with aliases |
| 157 | if (!importPath.startsWith('@/') && !importPath.startsWith('~/') && !importPath.startsWith('src/')) { |
| 158 | // Likely an npm package |
| 159 | return true; |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | if (language === 'python') { |
| 164 | // Standard library modules |
| 165 | const stdLibs = ['os', 'sys', 'json', 're', 'math', 'datetime', 'collections', 'typing', 'pathlib', 'logging']; |
| 166 | if (stdLibs.includes(importPath.split('.')[0]!)) { |
| 167 | return true; |
| 168 | } |
| 169 | } |
| 170 | |
| 171 | if (language === 'go') { |
| 172 | // Relative imports (rare in idiomatic Go but the grammar allows them). |
| 173 | if (importPath.startsWith('.')) { |
| 174 | return false; |
| 175 | } |
| 176 | // In-module imports look like `<module-path>/sub/pkg` — local to |
| 177 | // this project. Without the module-path check we'd flag every |
| 178 | // cross-package call in a Go monorepo as external (issue #388). |
| 179 | const mod = context?.getGoModule?.(); |
| 180 | if (mod && (importPath === mod.modulePath || importPath.startsWith(mod.modulePath + '/'))) { |
| 181 | return false; |
no test coverage detected