Spaces:
				
			
			
	
			
			
					
		Running
		
			on 
			
			CPU Upgrade
	
	
	
			
			
	
	
	
	
		
		
					
		Running
		
			on 
			
			CPU Upgrade
	File size: 5,081 Bytes
			
			| 7357f85 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | import fs from "fs";
import path from "path";
export default {
	meta: {
		type: "suggestion",
		docs: {
			description: "Enforce file extensions in import statements",
		},
		fixable: "code",
		schema: [
			{
				type: "object",
				properties: {
					ignorePaths: {
						type: "array",
						items: { type: "string" },
					},
					includePaths: {
						type: "array",
						items: { type: "string" },
						description: "Path patterns to include (e.g., '$lib/')",
					},
					tsToJs: {
						type: "boolean",
						description: "Convert .ts files to .js when importing",
					},
					aliases: {
						type: "object",
						description: "Map of path aliases to their actual paths (e.g., {'$lib': 'src/lib'})",
					},
				},
				additionalProperties: false,
			},
		],
		messages: {
			missingExtension: "Import should include a file extension",
			noFileFound: "Import is missing extension and no matching file was found",
		},
	},
	create(context) {
		const options = context.options[0] || {};
		const ignorePaths = options.ignorePaths || [];
		const includePaths = options.includePaths || [];
		const tsToJs = options.tsToJs !== undefined ? options.tsToJs : true; // Default to true
		const aliases = options.aliases || {};
		// Get the project root directory
		const projectRoot = process.cwd();
		// Utility function to resolve file paths
		function resolveImportPath(importPath, currentFilePath) {
			// Handle relative paths
			if (importPath.startsWith("./") || importPath.startsWith("../")) {
				return path.resolve(path.dirname(currentFilePath), importPath);
			}
			// Handle aliased paths
			for (const [alias, aliasPath] of Object.entries(aliases)) {
				// Check if the import starts with this alias
				if (importPath === alias || importPath.startsWith(`${alias}/`)) {
					// Replace the alias with the actual path
					const relativePath = importPath === alias ? "" : importPath.slice(alias.length + 1); // +1 for the slash
					// Convert the aliasPath to an absolute path
					let absoluteAliasPath = aliasPath;
					if (!path.isAbsolute(absoluteAliasPath)) {
						absoluteAliasPath = path.resolve(projectRoot, aliasPath);
					}
					return path.join(absoluteAliasPath, relativePath);
				}
			}
			return null;
		}
		// Find the file extension by checking which file exists
		function findActualFile(basePath) {
			if (!basePath) return null;
			try {
				// Get the directory and base name
				const dir = path.dirname(basePath);
				const base = path.basename(basePath);
				// If the directory doesn't exist, return early
				if (!fs.existsSync(dir)) {
					return null;
				}
				// Read all files in the directory
				const files = fs.readdirSync(dir);
				// Look for files that match our base name plus any extension
				for (const file of files) {
					const fileParts = path.parse(file);
					// If we find a file that matches our base name
					if (fileParts.name === base) {
						// Handle TypeScript to JavaScript conversion
						if (tsToJs && fileParts.ext === ".ts") {
							return {
								actualPath: path.join(dir, file),
								importExt: ".js", // Import as .js even though it's a .ts file
							};
						}
						// Otherwise use the actual extension
						return {
							actualPath: path.join(dir, file),
							importExt: fileParts.ext,
						};
					}
				}
			} catch (error) {
				// If there's an error checking file existence, return null
				console.error("Error checking files:", error);
			}
			return null;
		}
		return {
			ImportDeclaration(node) {
				const source = node.source.value;
				// Check if it's a relative import or matches a manually specified include path
				const isRelativeImport = source.startsWith("./") || source.startsWith("../");
				const isAliasedPath = Object.keys(aliases).some(alias => source === alias || source.startsWith(`${alias}/`));
				const isIncludedPath = includePaths.some(pattern => source.startsWith(pattern));
				// Skip if it's not a relative import, aliased path, or included path
				if (!isRelativeImport && !isAliasedPath && !isIncludedPath) {
					return;
				}
				// Skip ignored paths
				if (ignorePaths.some(path => source.includes(path))) {
					return;
				}
				// Check if the import already has an extension
				const hasExtension = path.extname(source) !== "";
				if (!hasExtension) {
					// Get current file path to resolve the import
					const currentFilePath = context.getFilename();
					// Try to determine the correct file by checking what exists
					const resolvedPath = resolveImportPath(source, currentFilePath);
					const fileInfo = findActualFile(resolvedPath);
					context.report({
						node,
						messageId: fileInfo ? "missingExtension" : "noFileFound",
						fix(fixer) {
							// Only provide a fix if we found a file
							if (fileInfo) {
								// Replace the string literal with one that includes the extension
								return fixer.replaceText(node.source, `"${source}${fileInfo.importExt}"`);
							}
							// Otherwise, don't try to fix
							return null;
						},
					});
				}
			},
		};
	},
};
 | 
