Skip to content

Commit 4e73b3b

Browse files
Added maximum type resolution limit
1 parent 41551ca commit 4e73b3b

File tree

4 files changed

+35
-13
lines changed

4 files changed

+35
-13
lines changed

typescript/src/core/concepts/type.concept.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ export class LCETypeNotIdentified extends LCEType {
201201
public static readonly SETTER = new this("Setter");
202202
public static readonly INDEXED_ACCESS_TYPE = new this("Type containing a (potentially recursive) indexed access type");
203203
public static readonly DEEP_PARTIAL_OBJECT = new this("Type containing DeepPartialObject (not supported)");
204+
public static readonly RESOLUTION_LIMIT = new this("Type that is beyond the set type resolution depth");
204205

205206
/**
206207
* @param identifier string representation of type that could not successfully be parsed

typescript/src/core/processors/type.utils.ts

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ import { NodeUtils } from "../utils/node.utils";
5555
import path from "path";
5656
import { FileUtils } from "../utils/file.utils";
5757

58+
let MAX_TYPE_RESOLUTION_DEPTH = 10;
59+
60+
export function setMaxTypeResolutionDepth(maxDepth: number) {
61+
MAX_TYPE_RESOLUTION_DEPTH = maxDepth;
62+
}
63+
5864
/**
5965
* Returns the type for a given class property (with a non-computed name)
6066
* @param esProperty property name node provided in ESTree
@@ -301,7 +307,13 @@ export function parseESNodeType(processingContext: ProcessingContext, esNode: ES
301307
return result;
302308
}
303309

304-
function parseType(processingContext: ProcessingContext, type: Type, node: Node, excludedFQN?: string, ignoreDependencies = false): LCEType {
310+
function parseType(processingContext: ProcessingContext, type: Type, node: Node, excludedFQN?: string, ignoreDependencies = false, typeResolutionDepth = 0): LCEType {
311+
312+
// cut off type resolution at a certain depth to prevent graph clutter and potential infinite recursion
313+
if(typeResolutionDepth > MAX_TYPE_RESOLUTION_DEPTH) {
314+
return LCETypeNotIdentified.RESOLUTION_LIMIT;
315+
}
316+
305317
const globalContext = processingContext.globalContext;
306318
const tc = globalContext.typeChecker;
307319
try {
@@ -341,7 +353,7 @@ function parseType(processingContext: ProcessingContext, type: Type, node: Node,
341353
}
342354

343355
// anonymous type
344-
return parseAnonymousType(processingContext, type, node, symbol, excludedFQN, ignoreDependencies);
356+
return parseAnonymousType(processingContext, type, node, symbol, excludedFQN, ignoreDependencies, typeResolutionDepth);
345357
}
346358

347359
if (type.isTypeParameter()) {
@@ -413,17 +425,17 @@ function parseType(processingContext: ProcessingContext, type: Type, node: Node,
413425

414426
if (normalizedFqn.globalFqn === excludedFQN) {
415427
// if declared type would reference excluded fqn (e.g. variable name), treat as anonymous type
416-
return parseAnonymousType(processingContext, type, node, symbol, excludedFQN, ignoreDependencies);
428+
return parseAnonymousType(processingContext, type, node, symbol, excludedFQN, ignoreDependencies, typeResolutionDepth);
417429
}
418430

419431
const typeArguments: LCEType[] = [];
420432
for (let i = 0; i < tc.getTypeArguments(type as TypeReference).length; i++) {
421433
const ta = tc.getTypeArguments(type as TypeReference)[i];
422434
if ("typeArguments" in node && node.typeArguments) {
423435
// if type argument child node is available, pass it on
424-
typeArguments.push(parseType(processingContext, ta, (node.typeArguments as Node[]).at(i) ?? node, excludedFQN, ignoreDependencies))
436+
typeArguments.push(parseType(processingContext, ta, (node.typeArguments as Node[]).at(i) ?? node, excludedFQN, ignoreDependencies, typeResolutionDepth + 1))
425437
} else {
426-
typeArguments.push(parseType(processingContext, ta, node, excludedFQN, ignoreDependencies));
438+
typeArguments.push(parseType(processingContext, ta, node, excludedFQN, ignoreDependencies, typeResolutionDepth + 1));
427439
}
428440
}
429441

@@ -451,23 +463,24 @@ function parseAnonymousType(
451463
node: Node,
452464
symbol?: Symbol,
453465
excludedFQN?: string,
454-
ignoreDependencies = false
466+
ignoreDependencies = false,
467+
typeResolutionDepth = 0
455468
): LCEType {
456469
const globalContext = processingContext.globalContext;
457470
const tc = globalContext.typeChecker;
458471

459472
// complex anonymous type
460473
if (type.isUnion()) {
461474
// union type
462-
return new LCETypeUnion(type.types.map((t) => parseType(processingContext, t, node, excludedFQN, ignoreDependencies)));
475+
return new LCETypeUnion(type.types.map((t) => parseType(processingContext, t, node, excludedFQN, ignoreDependencies, typeResolutionDepth + 1)));
463476
} else if (type.isIntersection()) {
464477
// intersection type
465-
return new LCETypeIntersection(type.types.map((t) => parseType(processingContext, t, node, excludedFQN, ignoreDependencies)));
478+
return new LCETypeIntersection(type.types.map((t) => parseType(processingContext, t, node, excludedFQN, ignoreDependencies, typeResolutionDepth + 1)));
466479
} else if (type.getCallSignatures().length > 0) {
467480
if (type.getCallSignatures().length > 1) return new LCETypeNotIdentified(tc.typeToString(type));
468481
// function type
469482
const signature = type.getCallSignatures()[0];
470-
const returnType = parseType(processingContext, tc.getReturnTypeOfSignature(signature), node, excludedFQN, ignoreDependencies);
483+
const returnType = parseType(processingContext, tc.getReturnTypeOfSignature(signature), node, excludedFQN, ignoreDependencies, typeResolutionDepth + 1);
471484
const parameters: LCETypeFunctionParameter[] = [];
472485
const paramSyms = signature.getParameters();
473486
for (let i = 0; i < paramSyms.length; i++) {
@@ -480,7 +493,7 @@ function parseAnonymousType(
480493
i,
481494
parameterSym.name,
482495
optional,
483-
parseType(processingContext, paramType, node, excludedFQN, ignoreDependencies)
496+
parseType(processingContext, paramType, node, excludedFQN, ignoreDependencies, typeResolutionDepth + 1)
484497
)
485498
);
486499
}
@@ -499,7 +512,7 @@ function parseAnonymousType(
499512
const propType = tc.getTypeOfSymbolAtLocation(prop, node);
500513
members.push(new LCETypeObjectMember(
501514
prop.name,
502-
parseType(processingContext, propType, node, undefined, ignoreDependencies),
515+
parseType(processingContext, propType, node, undefined, ignoreDependencies, typeResolutionDepth + 1),
503516
optional,
504517
readonly
505518
));
@@ -523,7 +536,7 @@ function parseAnonymousType(
523536
const typeArgs = tc.getTypeArguments(type as TypeReference);
524537
const types: LCEType[] = [];
525538
for (const typeArg of typeArgs) {
526-
types.push(parseType(processingContext, typeArg, node, excludedFQN, ignoreDependencies));
539+
types.push(parseType(processingContext, typeArg, node, excludedFQN, ignoreDependencies, typeResolutionDepth + 1));
527540
}
528541
return new LCETypeTuple(types);
529542
}

typescript/src/main.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import { program } from "commander";
44
import { processProjectsAndOutputResult } from "./core/extractor";
55
import packageInfo from "../package.json";
66
import { initializeReactExtractor } from "./react/react-extractor";
7-
import {setDebugLogging} from "./core/utils/log.utils";
7+
import { setDebugLogging } from "./core/utils/log.utils";
8+
import { setMaxTypeResolutionDepth } from "./core/processors/type.utils";
89

910

1011
// Setup CLI
@@ -16,6 +17,7 @@ program
1617
.argument("[path]", "path to the root of the TypeScript project to be scanned", ".")
1718
.option("-e, --extension [extensions...]", "space separated list of extensions to activate (currently available: react)")
1819
.option("-p, --pretty", "pretty-print JSON result report")
20+
.option("--type-resolution-depth", "depth at which nested types are no longer resolved", "10")
1921
.option("-d, --debug", "print debug information");
2022
program.parse();
2123

@@ -24,6 +26,7 @@ const options = program.opts();
2426

2527
const extensions: string[] = options.extension ?? [];
2628
const prettyPrint = !!options.pretty;
29+
setMaxTypeResolutionDepth(parseInt(options.typeResolutionDepth));
2730
setDebugLogging(!!options.debug);
2831

2932
const projectRootPath: string = program.processedArgs[0];

typescript/test/core/integration/sample-projects/type-alias-declarations/src/main.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,8 @@ type tRecursive = {
8888
a: string;
8989
r?: tRecursive;
9090
}
91+
92+
type tRecursiveComplex = {
93+
(x: number, y: (a: number | tRecursiveComplex, b: tRecursiveComplex) => string ): string;
94+
someProp: string;
95+
};

0 commit comments

Comments
 (0)