feat(cli): Add new check command for type checking and update compiler options handling
This commit is contained in:
@ -17,7 +17,7 @@ export const compilerOptionsDefault: CompilerOptions = {
|
||||
target: plugins.typescript.ScriptTarget.ESNext,
|
||||
moduleResolution: plugins.typescript.ModuleResolutionKind.NodeNext,
|
||||
lib: ['lib.dom.d.ts', 'lib.es2022.d.ts'],
|
||||
noImplicitAny: true,
|
||||
noImplicitAny: false, // Allow implicit any by default
|
||||
esModuleInterop: true,
|
||||
useDefineForClassFields: false,
|
||||
verbatimModuleSyntax: true,
|
||||
@ -49,7 +49,6 @@ export class TsBuild {
|
||||
* Helper function to read and process tsconfig.json
|
||||
*/
|
||||
private getTsConfigOptions(): CompilerOptions {
|
||||
console.log(`looking at tsconfig.json at ${paths.cwd}`);
|
||||
const tsconfig = plugins.smartfile.fs.toObjectSync(plugins.path.join(paths.cwd, 'tsconfig.json'));
|
||||
const returnObject: CompilerOptions = {};
|
||||
|
||||
@ -57,16 +56,66 @@ export class TsBuild {
|
||||
return returnObject;
|
||||
}
|
||||
|
||||
// Process baseUrl
|
||||
if (tsconfig.compilerOptions.baseUrl) {
|
||||
console.log('baseUrl found in tsconfig.json');
|
||||
returnObject.baseUrl = tsconfig.compilerOptions.baseUrl;
|
||||
}
|
||||
|
||||
// Process paths
|
||||
if (tsconfig.compilerOptions.paths) {
|
||||
console.log('paths found in tsconfig.json');
|
||||
returnObject.paths = tsconfig.compilerOptions.paths;
|
||||
for (const path of Object.keys(tsconfig.compilerOptions.paths)) {
|
||||
returnObject.paths[path][0] = returnObject.paths[path][0].replace('./ts_', './dist_ts_');
|
||||
returnObject.paths = { ...tsconfig.compilerOptions.paths };
|
||||
for (const path of Object.keys(returnObject.paths)) {
|
||||
if (Array.isArray(returnObject.paths[path]) && returnObject.paths[path].length > 0) {
|
||||
returnObject.paths[path][0] = returnObject.paths[path][0].replace('./ts_', './dist_ts_');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process target
|
||||
if (tsconfig.compilerOptions.target) {
|
||||
if (typeof tsconfig.compilerOptions.target === 'string') {
|
||||
const targetKey = tsconfig.compilerOptions.target.toUpperCase();
|
||||
if (targetKey in plugins.typescript.ScriptTarget) {
|
||||
returnObject.target = plugins.typescript.ScriptTarget[targetKey as keyof typeof plugins.typescript.ScriptTarget];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process module
|
||||
if (tsconfig.compilerOptions.module) {
|
||||
if (typeof tsconfig.compilerOptions.module === 'string') {
|
||||
const moduleKey = tsconfig.compilerOptions.module.toUpperCase();
|
||||
if (moduleKey in plugins.typescript.ModuleKind) {
|
||||
returnObject.module = plugins.typescript.ModuleKind[moduleKey as keyof typeof plugins.typescript.ModuleKind];
|
||||
} else if (moduleKey === 'NODENEXT') {
|
||||
returnObject.module = plugins.typescript.ModuleKind.NodeNext;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process moduleResolution
|
||||
if (tsconfig.compilerOptions.moduleResolution) {
|
||||
if (typeof tsconfig.compilerOptions.moduleResolution === 'string') {
|
||||
const moduleResolutionKey = tsconfig.compilerOptions.moduleResolution.toUpperCase();
|
||||
if (moduleResolutionKey in plugins.typescript.ModuleResolutionKind) {
|
||||
returnObject.moduleResolution = plugins.typescript.ModuleResolutionKind[
|
||||
moduleResolutionKey as keyof typeof plugins.typescript.ModuleResolutionKind
|
||||
];
|
||||
} else if (moduleResolutionKey === 'NODENEXT') {
|
||||
returnObject.moduleResolution = plugins.typescript.ModuleResolutionKind.NodeNext;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy boolean options directly
|
||||
const booleanOptions = [
|
||||
'experimentalDecorators', 'useDefineForClassFields',
|
||||
'esModuleInterop', 'verbatimModuleSyntax'
|
||||
];
|
||||
|
||||
for (const option of booleanOptions) {
|
||||
if (option in tsconfig.compilerOptions) {
|
||||
(returnObject as any)[option] = (tsconfig.compilerOptions as any)[option];
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,8 +134,9 @@ export class TsBuild {
|
||||
options.skipLibCheck = true;
|
||||
}
|
||||
|
||||
if (argvArg.allowimplicitany) {
|
||||
options.noImplicitAny = false;
|
||||
// Changed behavior: --disallowimplicitany instead of --allowimplicitany
|
||||
if (argvArg.disallowimplicitany) {
|
||||
options.noImplicitAny = true;
|
||||
}
|
||||
|
||||
if (argvArg.commonjs) {
|
||||
@ -112,7 +162,6 @@ export class TsBuild {
|
||||
...this.getTsConfigOptions(),
|
||||
};
|
||||
|
||||
console.log(mergedOptions);
|
||||
return mergedOptions;
|
||||
}
|
||||
|
||||
@ -120,22 +169,94 @@ export class TsBuild {
|
||||
* Helper function to handle and log TypeScript diagnostics
|
||||
*/
|
||||
private handleDiagnostics(diagnostics: readonly plugins.typescript.Diagnostic[]): boolean {
|
||||
let hasErrors = false;
|
||||
if (diagnostics.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Group errors by file for better readability
|
||||
const errorsByFile: Record<string, plugins.typescript.Diagnostic[]> = {};
|
||||
const generalErrors: plugins.typescript.Diagnostic[] = [];
|
||||
|
||||
// Categorize diagnostics
|
||||
diagnostics.forEach((diagnostic) => {
|
||||
hasErrors = true;
|
||||
if (diagnostic.file) {
|
||||
const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!);
|
||||
const message = plugins.typescript.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
|
||||
console.log(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`);
|
||||
const fileName = diagnostic.file.fileName;
|
||||
if (!errorsByFile[fileName]) {
|
||||
errorsByFile[fileName] = [];
|
||||
}
|
||||
errorsByFile[fileName].push(diagnostic);
|
||||
} else {
|
||||
console.log(
|
||||
`${plugins.typescript.flattenDiagnosticMessageText(diagnostic.messageText, '\n')}`
|
||||
);
|
||||
generalErrors.push(diagnostic);
|
||||
}
|
||||
});
|
||||
|
||||
return hasErrors;
|
||||
// Print error summary header
|
||||
const totalErrorCount = diagnostics.length;
|
||||
const fileCount = Object.keys(errorsByFile).length;
|
||||
|
||||
console.log('\n' + '='.repeat(80));
|
||||
console.log(`❌ Found ${totalErrorCount} error${totalErrorCount !== 1 ? 's' : ''} in ${fileCount} file${fileCount !== 1 ? 's' : ''}:`);
|
||||
console.log('='.repeat(80));
|
||||
|
||||
// Color codes for error formatting
|
||||
const colors = {
|
||||
reset: '\x1b[0m',
|
||||
red: '\x1b[31m',
|
||||
yellow: '\x1b[33m',
|
||||
cyan: '\x1b[36m',
|
||||
white: '\x1b[37m',
|
||||
brightRed: '\x1b[91m'
|
||||
};
|
||||
|
||||
// Print file-specific errors
|
||||
Object.entries(errorsByFile).forEach(([fileName, fileErrors]) => {
|
||||
// Show relative path if possible for cleaner output
|
||||
const displayPath = fileName.replace(process.cwd(), '').replace(/^\//, '');
|
||||
|
||||
console.log(`\n${colors.cyan}File: ${displayPath} ${colors.yellow}(${fileErrors.length} error${fileErrors.length !== 1 ? 's' : ''})${colors.reset}`);
|
||||
console.log('-'.repeat(80));
|
||||
|
||||
fileErrors.forEach((diagnostic) => {
|
||||
if (diagnostic.file && diagnostic.start !== undefined) {
|
||||
const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
|
||||
const message = plugins.typescript.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
|
||||
const errorCode = diagnostic.code ? `TS${diagnostic.code}` : 'Error';
|
||||
|
||||
console.log(`${colors.white}Line ${line + 1}, Col ${character + 1}${colors.reset}: ${colors.brightRed}${errorCode}${colors.reset} - ${message}`);
|
||||
|
||||
// Try to show the code snippet if possible
|
||||
try {
|
||||
const lineContent = diagnostic.file.text.split('\n')[line];
|
||||
if (lineContent) {
|
||||
// Show the line of code
|
||||
console.log(` ${lineContent.trimRight()}`);
|
||||
|
||||
// Show the error position indicator
|
||||
const indicator = ' '.repeat(character) + `${colors.red}^${colors.reset}`;
|
||||
console.log(` ${indicator}`);
|
||||
}
|
||||
} catch (e) {
|
||||
// Failed to get source text, skip showing the code snippet
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Print general errors
|
||||
if (generalErrors.length > 0) {
|
||||
console.log(`\n${colors.yellow}General Errors:${colors.reset}`);
|
||||
console.log('-'.repeat(80));
|
||||
|
||||
generalErrors.forEach((diagnostic) => {
|
||||
const message = plugins.typescript.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
|
||||
const errorCode = diagnostic.code ? `TS${diagnostic.code}` : 'Error';
|
||||
console.log(`${colors.brightRed}${errorCode}${colors.reset}: ${message}`);
|
||||
});
|
||||
}
|
||||
|
||||
console.log('\n' + '='.repeat(80) + '\n');
|
||||
|
||||
return diagnostics.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -166,14 +287,13 @@ export class TsBuild {
|
||||
*/
|
||||
public async compile(): Promise<any[]> {
|
||||
if (this.options.skipLibCheck) {
|
||||
console.log('? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?');
|
||||
console.log('\n⚠️ WARNING ⚠️');
|
||||
console.log('You are skipping libcheck... Is that really wanted?');
|
||||
console.log('continuing in 5 seconds...');
|
||||
console.log('? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?');
|
||||
console.log('Continuing in 5 seconds...\n');
|
||||
await plugins.smartdelay.delayFor(5000);
|
||||
}
|
||||
|
||||
console.log(`Compiling ${this.fileNames.length} files...`);
|
||||
console.log(`🔨 Compiling ${this.fileNames.length} files...`);
|
||||
const done = plugins.smartpromise.defer<any[]>();
|
||||
const program = this.createProgram();
|
||||
|
||||
@ -183,7 +303,8 @@ export class TsBuild {
|
||||
|
||||
// Only continue to emit phase if no pre-emit errors
|
||||
if (hasPreEmitErrors) {
|
||||
console.error('TypeScript pre-emit checks failed. Please fix the issues above.');
|
||||
console.error('\n❌ TypeScript pre-emit checks failed. Please fix the issues listed above before proceeding.');
|
||||
console.error(' Type errors must be resolved before the compiler can emit output files.\n');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@ -193,10 +314,22 @@ export class TsBuild {
|
||||
|
||||
const exitCode = emitResult.emitSkipped ? 1 : 0;
|
||||
if (exitCode === 0) {
|
||||
console.log('TypeScript emit succeeded!');
|
||||
console.log('\n✅ TypeScript emit succeeded!');
|
||||
|
||||
// Get count of emitted files by type
|
||||
const jsFiles = emitResult.emittedFiles?.filter(f => f.endsWith('.js')).length || 0;
|
||||
const dtsFiles = emitResult.emittedFiles?.filter(f => f.endsWith('.d.ts')).length || 0;
|
||||
const mapFiles = emitResult.emittedFiles?.filter(f => f.endsWith('.map')).length || 0;
|
||||
|
||||
// If we have emitted files, show a summary
|
||||
if (emitResult.emittedFiles && emitResult.emittedFiles.length > 0) {
|
||||
console.log(` Generated ${emitResult.emittedFiles.length} files: ${jsFiles} .js, ${dtsFiles} .d.ts, ${mapFiles} source maps`);
|
||||
}
|
||||
|
||||
done.resolve(emitResult.emittedFiles);
|
||||
} else {
|
||||
console.error('TypeScript emit failed. Please investigate!');
|
||||
console.error('\n❌ TypeScript emit failed. Please investigate the errors listed above!');
|
||||
console.error(' No output files have been generated.\n');
|
||||
process.exit(exitCode);
|
||||
}
|
||||
|
||||
@ -207,7 +340,8 @@ export class TsBuild {
|
||||
* Function to check if files can be emitted without actually emitting them
|
||||
*/
|
||||
public async checkEmit(): Promise<boolean> {
|
||||
console.log(`Checking if ${this.fileNames.length} files can be emitted...`);
|
||||
const fileCount = this.fileNames.length;
|
||||
console.log(`\n🔍 Checking if ${fileCount} file${fileCount !== 1 ? 's' : ''} can be emitted...`);
|
||||
|
||||
// Create a program with noEmit option
|
||||
const program = this.createProgram({
|
||||
@ -226,9 +360,42 @@ export class TsBuild {
|
||||
const success = !hasPreEmitErrors && !hasEmitErrors && !emitResult.emitSkipped;
|
||||
|
||||
if (success) {
|
||||
console.log('TypeScript emit check passed! File can be emitted successfully.');
|
||||
console.log('\n✅ TypeScript emit check passed! All files can be emitted successfully.');
|
||||
console.log(` ${fileCount} file${fileCount !== 1 ? 's' : ''} ${fileCount !== 1 ? 'are' : 'is'} ready to be compiled.\n`);
|
||||
} else {
|
||||
console.error('TypeScript emit check failed. Please fix the issues above.');
|
||||
console.error('\n❌ TypeScript emit check failed. Please fix the issues listed above.');
|
||||
console.error(' The compilation cannot proceed until these errors are resolved.\n');
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to check TypeScript files for type errors without emission
|
||||
*/
|
||||
public async checkTypes(): Promise<boolean> {
|
||||
const fileCount = this.fileNames.length;
|
||||
console.log(`\n🔍 Type checking ${fileCount} TypeScript file${fileCount !== 1 ? 's' : ''}...`);
|
||||
|
||||
// Create a program with noEmit option explicitly set
|
||||
const program = this.createProgram({
|
||||
...this.options,
|
||||
noEmit: true
|
||||
});
|
||||
|
||||
// Check for type errors
|
||||
const diagnostics = plugins.typescript.getPreEmitDiagnostics(program);
|
||||
const hasErrors = this.handleDiagnostics(diagnostics);
|
||||
|
||||
// Set success flag
|
||||
const success = !hasErrors;
|
||||
|
||||
if (success) {
|
||||
console.log('\n✅ TypeScript type check passed! No type errors found.');
|
||||
console.log(` All ${fileCount} file${fileCount !== 1 ? 's' : ''} passed type checking successfully.\n`);
|
||||
} else {
|
||||
console.error('\n❌ TypeScript type check failed. Please fix the type errors listed above.');
|
||||
console.error(' The type checker found issues that need to be resolved.\n');
|
||||
}
|
||||
|
||||
return success;
|
||||
@ -268,4 +435,16 @@ export const emitCheck = async (
|
||||
): Promise<boolean> => {
|
||||
const tsBuild = new TsBuild(fileNames, options, argvArg);
|
||||
return tsBuild.checkEmit();
|
||||
};
|
||||
|
||||
/**
|
||||
* Function to check TypeScript files for type errors without emission (backward compatibility)
|
||||
*/
|
||||
export const checkTypes = async (
|
||||
fileNames: string[],
|
||||
options: plugins.typescript.CompilerOptions = {},
|
||||
argvArg?: any
|
||||
): Promise<boolean> => {
|
||||
const tsBuild = new TsBuild(fileNames, options, argvArg);
|
||||
return tsBuild.checkTypes();
|
||||
};
|
Reference in New Issue
Block a user