/// import ts from "typescript"; /** * Test TypeScript inference with STRICT mode enabled * Compare to non-strict mode to see differences */ const code = ` // Test cases that behave differently in strict vs non-strict mode // 1. Unused function parameter function unusedParam(x) { return 42; } // 2. Catch clause variable try { throw new Error("test"); } catch (e) { console.log(e); } // 3. Callback with no annotation [1, 2, 3].forEach(function(item) { console.log(item); }); // 4. Variable with no initializer let noInit; // 5. Class field with no type class TestClass { field; constructor(arg) { this.field = arg; } } // 6. Object with undefined property const obj = { x: undefined }; // 7. Empty array const arr = []; // 8. Promise executor parameters new Promise((resolve, reject) => { resolve(42); }); // 9. Generic function parameter function identity(x: T) { return x; } // 10. Rest parameters function rest(...args) { return args; } `; function analyzeWithMode(strict: boolean) { const fileName = "test.ts"; const sourceFile = ts.createSourceFile( fileName, code, ts.ScriptTarget.Latest, true, ); const options: ts.CompilerOptions = { target: ts.ScriptTarget.Latest, module: ts.ModuleKind.ESNext, strict: strict, noImplicitAny: false, // Still allow implicit any even in strict mode }; const host = ts.createCompilerHost(options); host.getSourceFile = (name) => name === fileName ? sourceFile : undefined; host.writeFile = () => {}; host.getCurrentDirectory = () => ""; host.getCanonicalFileName = (name) => name; host.useCaseSensitiveFileNames = () => true; host.getNewLine = () => "\n"; const program = ts.createProgram([fileName], options, host); const checker = program.getTypeChecker(); const results: Array< { name: string; typeString: string; isAny: boolean; isUnknown: boolean } > = []; function visit(node: ts.Node) { if ( ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node) ) { const funcName = ts.isFunctionDeclaration(node) && node.name ? node.name.text : "anonymous"; node.parameters.forEach((param) => { if (ts.isIdentifier(param.name)) { const type = checker.getTypeAtLocation(param); results.push({ name: `${funcName}::${param.name.text}`, typeString: checker.typeToString(type), isAny: !!(type.flags & ts.TypeFlags.Any), isUnknown: !!(type.flags & ts.TypeFlags.Unknown), }); } }); } if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) { const type = checker.getTypeAtLocation(node); results.push({ name: `var::${node.name.text}`, typeString: checker.typeToString(type), isAny: !!(type.flags & ts.TypeFlags.Any), isUnknown: !!(type.flags & ts.TypeFlags.Unknown), }); } if (ts.isCatchClause(node) && node.variableDeclaration) { const varDecl = node.variableDeclaration; if (ts.isIdentifier(varDecl.name)) { const type = checker.getTypeAtLocation(varDecl); results.push({ name: `catch::${varDecl.name.text}`, typeString: checker.typeToString(type), isAny: !!(type.flags & ts.TypeFlags.Any), isUnknown: !!(type.flags & ts.TypeFlags.Unknown), }); } } if (ts.isPropertyDeclaration(node) && ts.isIdentifier(node.name)) { const className = node.parent && ts.isClassDeclaration(node.parent) && node.parent.name ? node.parent.name.text : "anonymous"; const type = checker.getTypeAtLocation(node); results.push({ name: `${className}.${node.name.text}`, typeString: checker.typeToString(type), isAny: !!(type.flags & ts.TypeFlags.Any), isUnknown: !!(type.flags & ts.TypeFlags.Unknown), }); } ts.forEachChild(node, visit); } visit(sourceFile); return results; } console.log("=".repeat(80)); console.log("TypeScript Type Inference: Strict vs Non-Strict Mode Comparison"); console.log("=".repeat(80)); const nonStrictResults = analyzeWithMode(false); const strictResults = analyzeWithMode(true); console.log("\n" + "=".repeat(80)); console.log("COMPARISON TABLE"); console.log("=".repeat(80)); console.log( "\nIdentifier | Non-Strict Mode | Strict Mode", ); console.log("-".repeat(80)); for (let i = 0; i < nonStrictResults.length; i++) { const nonStrict = nonStrictResults[i]; const strict = strictResults[i]; const identifier = nonStrict.name.padEnd(35); const nonStrictType = nonStrict.typeString.padEnd(15); const strictType = strict.typeString.padEnd(15); const diff = nonStrict.typeString !== strict.typeString ? " ⚠️ DIFFERENT" : ""; console.log(`${identifier} | ${nonStrictType} | ${strictType}${diff}`); } console.log("\n" + "=".repeat(80)); console.log("SUMMARY"); console.log("=".repeat(80)); const nonStrictAny = nonStrictResults.filter((r) => r.isAny).length; const strictAny = strictResults.filter((r) => r.isAny).length; const nonStrictUnknown = nonStrictResults.filter((r) => r.isUnknown).length; const strictUnknown = strictResults.filter((r) => r.isUnknown).length; console.log(`\nNon-Strict Mode:`); console.log( ` 'any' inferences: ${nonStrictAny} / ${nonStrictResults.length} (${ (nonStrictAny / nonStrictResults.length * 100).toFixed(1) }%)`, ); console.log( ` 'unknown' inferences: ${nonStrictUnknown} / ${nonStrictResults.length} (${ (nonStrictUnknown / nonStrictResults.length * 100).toFixed(1) }%)`, ); console.log(`\nStrict Mode:`); console.log( ` 'any' inferences: ${strictAny} / ${strictResults.length} (${ (strictAny / strictResults.length * 100).toFixed(1) }%)`, ); console.log( ` 'unknown' inferences: ${strictUnknown} / ${strictResults.length} (${ (strictUnknown / strictResults.length * 100).toFixed(1) }%)`, ); // Find specific cases where strict mode changes behavior console.log("\n" + "=".repeat(80)); console.log("NOTABLE DIFFERENCES"); console.log("=".repeat(80)); const differences = []; for (let i = 0; i < nonStrictResults.length; i++) { const nonStrict = nonStrictResults[i]; const strict = strictResults[i]; if (nonStrict.typeString !== strict.typeString) { differences.push({ name: nonStrict.name, nonStrict: nonStrict.typeString, strict: strict.typeString, }); } } if (differences.length === 0) { console.log("\nNo differences found between strict and non-strict modes."); } else { for (const diff of differences) { console.log(`\n${diff.name}:`); console.log(` Non-Strict: ${diff.nonStrict}`); console.log(` Strict: ${diff.strict}`); } } // Check for unknown inference console.log("\n" + "=".repeat(80)); console.log("CASES WHERE 'unknown' IS INFERRED"); console.log("=".repeat(80)); const unknownCases = [...nonStrictResults, ...strictResults].filter((r) => r.isUnknown ); if (unknownCases.length === 0) { console.log("\nNo cases where 'unknown' was inferred."); console.log( "TypeScript strongly prefers 'any' for implicit/underspecified types.", ); } else { for (const unk of unknownCases) { console.log(`\n${unk.name}: ${unk.typeString}`); } }