1313 * Features:
1414 * - Scans all .ts and .tsx files in the src/ directory recursively
1515 * - Detects relative imports (starting with ./ or ../) without extensions
16+ * - Prevents .js/.jsx imports in TypeScript files (should use .ts/.tsx)
1617 * - Ignores commented code and CSS imports (handled by bundlers)
1718 * - Provides detailed error reporting with line numbers and suggestions
1819 * - Exits with appropriate status codes for CI/CD integration
2425 * 0 - All relative imports have valid extensions
2526 * 1 - Found relative imports without extensions
2627 *
27- * Valid extensions: .ts, .tsx, .js, .jsx, .json
28+ * Valid extensions for TypeScript files: .ts, .tsx, .json
29+ * Invalid extensions for TypeScript files: .js, .jsx (use .ts/.tsx instead)
2830 *
2931 * @example
3032 * // ❌ Invalid - missing extension
3133 * import { config } from "./config/index";
3234 *
33- * // ✅ Valid - has extension
35+ * // ❌ Invalid - .js extension in TypeScript file
36+ * import { config } from "./config/index.js";
37+ *
38+ * // ✅ Valid - has .ts extension
3439 * import { config } from "./config/index.ts";
3540 *
3641 * // ✅ Valid - external package import (no extension needed)
@@ -46,7 +51,8 @@ const __dirname = dirname(__filename);
4651
4752// Configuration
4853const SRC_DIR = join ( __dirname , ".." , "src" ) ;
49- const VALID_EXTENSIONS = [ ".ts" , ".tsx" , ".js" , ".jsx" , ".json" ] ;
54+ const VALID_EXTENSIONS = [ ".ts" , ".tsx" , ".json" ] ;
55+ const INVALID_EXTENSIONS = [ ".js" , ".jsx" ] ;
5056const FILE_EXTENSIONS = [ ".ts" , ".tsx" ] ;
5157
5258/**
@@ -87,6 +93,13 @@ function hasValidExtension(path) {
8793 return VALID_EXTENSIONS . some ( ext => path . endsWith ( ext ) ) ;
8894}
8995
96+ /**
97+ * Check if an import path has an invalid extension for TypeScript files
98+ */
99+ function hasInvalidExtension ( path ) {
100+ return INVALID_EXTENSIONS . some ( ext => path . endsWith ( ext ) ) ;
101+ }
102+
90103/**
91104 * Extract import/export statements from file content
92105 * Matches both import and export statements with from clauses
@@ -154,13 +167,24 @@ function validateFile(filePath) {
154167 const statements = extractImportExportStatements ( content , filePath ) ;
155168
156169 statements . forEach ( statement => {
157- if ( isRelativeImport ( statement . path ) && ! hasValidExtension ( statement . path ) ) {
158- errors . push ( {
159- file : filePath ,
160- line : statement . line ,
161- importPath : statement . path ,
162- fullLine : statement . fullLine
163- } ) ;
170+ if ( isRelativeImport ( statement . path ) ) {
171+ if ( hasInvalidExtension ( statement . path ) ) {
172+ errors . push ( {
173+ file : filePath ,
174+ line : statement . line ,
175+ importPath : statement . path ,
176+ fullLine : statement . fullLine ,
177+ type : "invalid-extension"
178+ } ) ;
179+ } else if ( ! hasValidExtension ( statement . path ) ) {
180+ errors . push ( {
181+ file : filePath ,
182+ line : statement . line ,
183+ importPath : statement . path ,
184+ fullLine : statement . fullLine ,
185+ type : "missing-extension"
186+ } ) ;
187+ }
164188 }
165189 } ) ;
166190 } catch ( error ) {
@@ -203,26 +227,85 @@ function validateImports() {
203227 console . log ( "✅ All relative imports have valid extensions!" ) ;
204228 process . exit ( 0 ) ;
205229 } else {
206- console . log ( `❌ Found ${ totalErrors } relative import(s) without extensions:\n` ) ;
207-
208- errorsByFile . forEach ( ( errors , file ) => {
209- const relativePath = file . replace ( process . cwd ( ) , "" ) . replace ( / ^ \/ / , "" ) ;
210- console . log ( `📄 ${ relativePath } :` ) ;
230+ const missingExtensionErrors = [ ] ;
231+ const invalidExtensionErrors = [ ] ;
211232
233+ errorsByFile . forEach ( errors => {
212234 errors . forEach ( error => {
213- console . log ( ` Line ${ error . line } : ${ error . importPath } ` ) ;
214- console . log ( ` ${ error . fullLine } ` ) ;
215- console . log (
216- ` ${ "" . padStart ( error . fullLine . indexOf ( error . importPath ) , " " ) } ${ "" . padStart ( error . importPath . length , "^" ) } `
217- ) ;
235+ if ( error . type === "missing-extension" ) {
236+ missingExtensionErrors . push ( error ) ;
237+ } else if ( error . type === "invalid-extension" ) {
238+ invalidExtensionErrors . push ( error ) ;
239+ }
218240 } ) ;
219- console . log ( "" ) ;
220241 } ) ;
221242
243+ if ( missingExtensionErrors . length > 0 ) {
244+ console . log (
245+ `❌ Found ${ missingExtensionErrors . length } relative import(s) without extensions:\n`
246+ ) ;
247+
248+ const missingByFile = new Map ( ) ;
249+ missingExtensionErrors . forEach ( error => {
250+ if ( ! missingByFile . has ( error . file ) ) {
251+ missingByFile . set ( error . file , [ ] ) ;
252+ }
253+ missingByFile . get ( error . file ) . push ( error ) ;
254+ } ) ;
255+
256+ missingByFile . forEach ( ( errors , file ) => {
257+ const relativePath = file . replace ( process . cwd ( ) , "" ) . replace ( / ^ \/ / , "" ) ;
258+ console . log ( `📄 ${ relativePath } :` ) ;
259+
260+ errors . forEach ( error => {
261+ console . log ( ` Line ${ error . line } : ${ error . importPath } ` ) ;
262+ console . log ( ` ${ error . fullLine } ` ) ;
263+ console . log (
264+ ` ${ "" . padStart ( error . fullLine . indexOf ( error . importPath ) , " " ) } ${ "" . padStart ( error . importPath . length , "^" ) } `
265+ ) ;
266+ } ) ;
267+ console . log ( "" ) ;
268+ } ) ;
269+ }
270+
271+ if ( invalidExtensionErrors . length > 0 ) {
272+ console . log (
273+ `❌ Found ${ invalidExtensionErrors . length } relative import(s) with invalid extensions:\n`
274+ ) ;
275+
276+ const invalidByFile = new Map ( ) ;
277+ invalidExtensionErrors . forEach ( error => {
278+ if ( ! invalidByFile . has ( error . file ) ) {
279+ invalidByFile . set ( error . file , [ ] ) ;
280+ }
281+ invalidByFile . get ( error . file ) . push ( error ) ;
282+ } ) ;
283+
284+ invalidByFile . forEach ( ( errors , file ) => {
285+ const relativePath = file . replace ( process . cwd ( ) , "" ) . replace ( / ^ \/ / , "" ) ;
286+ console . log ( `📄 ${ relativePath } :` ) ;
287+
288+ errors . forEach ( error => {
289+ console . log ( ` Line ${ error . line } : ${ error . importPath } ` ) ;
290+ console . log ( ` ${ error . fullLine } ` ) ;
291+ console . log (
292+ ` ${ "" . padStart ( error . fullLine . indexOf ( error . importPath ) , " " ) } ${ "" . padStart ( error . importPath . length , "^" ) } `
293+ ) ;
294+ } ) ;
295+ console . log ( "" ) ;
296+ } ) ;
297+ }
298+
222299 console . log ( "💡 Tips:" ) ;
223- console . log ( ' - Add file extensions to relative imports (e.g., "./file" → "./file.ts")' ) ;
300+ if ( missingExtensionErrors . length > 0 ) {
301+ console . log ( ' - Add file extensions to relative imports (e.g., "./file" → "./file.ts")' ) ;
302+ }
303+ if ( invalidExtensionErrors . length > 0 ) {
304+ console . log ( " - Replace .js/.jsx with .ts/.tsx in TypeScript files" ) ;
305+ }
224306 console . log ( " - This is required by Node.js ESM modules" ) ;
225- console . log ( " - Valid extensions: " + VALID_EXTENSIONS . join ( ", " ) ) ;
307+ console . log ( " - Valid extensions for TypeScript files: " + VALID_EXTENSIONS . join ( ", " ) ) ;
308+ console . log ( " - Invalid extensions for TypeScript files: " + INVALID_EXTENSIONS . join ( ", " ) ) ;
226309
227310 process . exit ( 1 ) ;
228311 }
0 commit comments