/// import ts from "typescript"; /** * Focused exploration: Unused/unreferenced parameters and object fields */ const testCases = [ { name: "Completely unused function parameter", code: `function test(x) { return 42; }`, }, { name: "Parameter used only in console.log", code: `function test(x) { console.log(x); }`, }, { name: "Parameter returned directly", code: `function test(x) { return x; }`, }, { name: "Parameter used in arithmetic", code: `function test(x) { return x + 1; }`, }, { name: "Parameter used in string concatenation", code: `function test(x) { return x + "hello"; }`, }, { name: "Parameter used in comparison", code: `function test(x) { return x > 5; }`, }, { name: "Parameter passed to another function", code: `function test(x) { return JSON.stringify(x); }`, }, { name: "Parameter property accessed", code: `function test(x) { return x.prop; }`, }, { name: "Handler with unused event parameter", code: `const handler = (_, state) => state.value++;`, }, { name: "Handler with no event type, unused event", code: `function handler(e, state) { return state; }`, }, { name: "Object field - undefined literal", code: `const obj = { x: undefined };`, }, { name: "Object field - null literal", code: `const obj = { x: null };`, }, { name: "Object field - no initializer in interface", code: `interface I { x?; } const obj: I = {};`, }, { name: "Object field - optional in type literal", code: `const obj: { x?: number } = {};`, }, { name: "Class field - no type, no initializer", code: `class C { field; }`, }, { name: "Class field - initialized to undefined", code: `class C { field = undefined; }`, }, { name: "Class field - optional, no initializer", code: `class C { field?; }`, }, { name: "Destructured parameter - unused", code: `function test({ x }) { return 42; }`, }, { name: "Destructured parameter - used", code: `function test({ x }) { return x; }`, }, { name: "Rest parameter - unused", code: `function test(...args) { return 42; }`, }, { name: "Rest parameter - used", code: `function test(...args) { return args.length; }`, }, { name: "Array callback - unused parameter", code: `[1,2,3].map((x) => 42);`, }, { name: "Array callback - used parameter", code: `[1,2,3].map((x) => x * 2);`, }, { name: "Array callback - unused index", code: `[1,2,3].map((x, i) => x * 2);`, }, { name: "Type parameter - unused", code: `function test(x: string) { return x; }`, }, { name: "Type parameter - used in return", code: `function test(x: T): T { return x; }`, }, ]; function analyzeCase( testCase: { name: string; code: string }, strict: boolean, ) { const fileName = "test.ts"; const sourceFile = ts.createSourceFile( fileName, testCase.code, ts.ScriptTarget.Latest, true, ); const options: ts.CompilerOptions = { target: ts.ScriptTarget.Latest, module: ts.ModuleKind.ESNext, strict: strict, noImplicitAny: false, }; 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: string[] = []; function visit(node: ts.Node) { // Function/method parameters if ( ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node) || ts.isMethodDeclaration(node) ) { node.parameters.forEach((param) => { const type = checker.getTypeAtLocation(param); const typeStr = checker.typeToString(type); const paramName = ts.isIdentifier(param.name) ? param.name.text : ts.isObjectBindingPattern(param.name) ? "{...}" : "...rest"; results.push(`param '${paramName}': ${typeStr}`); }); // Type parameters if ("typeParameters" in node && node.typeParameters) { node.typeParameters.forEach((tp) => { const type = checker.getTypeAtLocation(tp); const typeStr = checker.typeToString(type); results.push(`typeParam '${tp.name.text}': ${typeStr}`); }); } } // Variable declarations if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) { const type = checker.getTypeAtLocation(node); const typeStr = checker.typeToString(type); results.push(`var '${node.name.text}': ${typeStr}`); } // Class fields if (ts.isPropertyDeclaration(node) && ts.isIdentifier(node.name)) { const type = checker.getTypeAtLocation(node); const typeStr = checker.typeToString(type); results.push(`field '${node.name.text}': ${typeStr}`); } ts.forEachChild(node, visit); } visit(sourceFile); return results; } console.log("=".repeat(80)); console.log("Focused Type Inference Analysis"); console.log("=".repeat(80)); for (const testCase of testCases) { const nonStrict = analyzeCase(testCase, false); const strict = analyzeCase(testCase, true); console.log(`\n${testCase.name}`); console.log(`Code: ${testCase.code}`); console.log("-".repeat(80)); for (let i = 0; i < nonStrict.length; i++) { const ns = nonStrict[i]; const s = strict[i]; if (ns === s) { console.log(` ${ns}`); } else { console.log(` Non-Strict: ${ns}`); console.log(` Strict: ${s}`); } } } // Summary analysis console.log("\n" + "=".repeat(80)); console.log("KEY FINDINGS"); console.log("=".repeat(80)); console.log(` 1. UNUSED FUNCTION PARAMETERS: - TypeScript infers 'any' for unused parameters in both strict and non-strict mode - Usage doesn't matter - even completely unused params get 'any' - This is true even when the parameter is never referenced 2. OBJECT FIELDS: - Fields initialized to 'undefined' get 'any' in non-strict, 'undefined' in strict - Fields initialized to 'null' get 'any' (the literal null type has type 'any') - Optional fields (?) without initializer get 'undefined' type 3. CATCH CLAUSE EXCEPTION: - The ONLY case where TypeScript infers 'unknown' instead of 'any' - Non-strict: catch (e) => e is 'any' - Strict: catch (e) => e is 'unknown' - This changed in TypeScript 4.0 as a safety improvement 4. HANDLER PATTERN (event, state): - Unused event parameter (often '_') gets 'any' - State parameter gets 'any' if not annotated - No special inference for common patterns 5. TYPE PARAMETERS: - Type parameters like are not 'any' or 'unknown' - They're type variables (TypeFlags = 262144 = TypeParameter) - Can be constrained but default to no constraint 6. CONTEXTUAL TYPING: - Array callbacks get contextual typing from the array - [1,2,3].map(x => ...) => x is inferred as 'number' from array - But the type checker shows 'any' for the parameter node itself `);