Skip to content

Commit 26a0956

Browse files
authored
fix: modify Package mapping logic (#103)
* fix bug: cannot identify export default arrow function * fix bug: if two files has same symbol name, should give a mangled name * 1. revert import path changes 2. fix bug: falsy caching strategy in function, * chore: modify package version * modify Package mapping logic * build symbol cache based on file, not on its directory
1 parent 7b4eb5b commit 26a0956

File tree

10 files changed

+206
-93
lines changed

10 files changed

+206
-93
lines changed

ts-parser/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "abcoder-ts-parser",
3-
"version": "0.0.8",
4-
"description": "TypeScript AST parser for UNIAST v0.1.3 specification",
3+
"version": "0.0.9",
4+
"description": "TypeScript AST parser for UNIAST specification",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",
77
"bin": {

ts-parser/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ const program = new Command();
99

1010
program
1111
.name('abcoder-ts-parser')
12-
.description('TypeScript AST parser for UNIAST v0.1.3 specification')
13-
.version('0.0.4');
12+
.description('TypeScript AST parser for UNIAST specification')
13+
.version('0.0.9');
1414

1515
program
1616
.command('parse')

ts-parser/src/parser/RepositoryParser.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,10 @@ export class RepositoryParser {
153153
private async processPackagesWithClusterMode(
154154
packages: MonorepoPackage[],
155155
repository: Repository,
156+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
156157
options: any
157158
): Promise<void> {
159+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
158160
if ((cluster as any).isPrimary || (cluster as any).isMaster) {
159161
const result = await processPackagesWithCluster(packages, this.projectRoot, options);
160162

ts-parser/src/parser/test/FunctionParser.test.ts

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -317,23 +317,70 @@ describe('FunctionParser', () => {
317317
second() { return this; }
318318
third() { return 'done'; }
319319
}
320-
320+
321321
function chainUser() {
322322
new Chain().first().second().third();
323323
}
324324
`);
325-
325+
326326
const parser = new FunctionParser(project, process.cwd());
327327
let pkgPathAbsFile : string = sourceFile.getFilePath()
328328
pkgPathAbsFile = pkgPathAbsFile.split('/').slice(0, -1).join('/')
329329
const pkgPath = path.relative(process.cwd(), pkgPathAbsFile)
330-
331-
330+
331+
332332
const functions = parser.parseFunctions(sourceFile, 'parser-tests', pkgPath);
333-
333+
334334
const chainUser = expectToBeDefined(functions['chainUser']);
335335
expect(chainUser.MethodCalls).toBeDefined();
336-
336+
337+
cleanup();
338+
});
339+
340+
it('should extract method calls on parameter objects', () => {
341+
const { project, sourceFile, cleanup } = createTestProject(`
342+
interface Context {
343+
logError(message: string, error: any): void;
344+
render(template: string, data: any): Promise<void>;
345+
status: number;
346+
}
347+
348+
function getConfig(key: string, ctx: any): Promise<any> {
349+
return Promise.resolve({ value: 'default' });
350+
}
351+
352+
async function handleRequest(ctx: Context, error: any) {
353+
if (error.code === 404) {
354+
ctx.status = 404;
355+
return;
356+
}
357+
ctx.logError('Error occurred: ' + error.message, error);
358+
const config = await getConfig('app.config', ctx);
359+
await ctx.render('error-page', { message: config.value || error.message || '' });
360+
ctx.status = 500;
361+
}
362+
`);
363+
364+
const parser = new FunctionParser(project, process.cwd());
365+
let pkgPathAbsFile : string = sourceFile.getFilePath()
366+
pkgPathAbsFile = pkgPathAbsFile.split('/').slice(0, -1).join('/')
367+
const pkgPath = path.relative(process.cwd(), pkgPathAbsFile)
368+
369+
const functions = parser.parseFunctions(sourceFile, 'parser-tests', pkgPath);
370+
371+
const handleRequest = expectToBeDefined(functions['handleRequest']);
372+
expect(handleRequest.MethodCalls).toBeDefined();
373+
374+
const methodCallNames = expectToBeDefined(handleRequest.MethodCalls).map(call => call.Name);
375+
// Method calls on parameter objects should include the interface name prefix
376+
expect(methodCallNames).toContain('Context.logError');
377+
expect(methodCallNames).toContain('Context.render');
378+
379+
// Should also have function calls
380+
expect(handleRequest.FunctionCalls).toBeDefined();
381+
const functionCallNames = expectToBeDefined(handleRequest.FunctionCalls).map(call => call.Name);
382+
expect(functionCallNames).toContain('getConfig');
383+
337384
cleanup();
338385
});
339386
});

ts-parser/src/parser/test/RepositoryParser.test.ts

Lines changed: 75 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -248,31 +248,42 @@ export interface ServerConfig {
248248
const sharedUtilsModule = result.Modules['@test/shared-utils'];
249249
expect(sharedUtilsModule).toBeDefined();
250250
expect(sharedUtilsModule.Packages).toBeDefined();
251-
expect(sharedUtilsModule.Packages.src).toBeDefined();
251+
252+
// Collect all functions, types, and vars from all file-based packages
253+
const allFunctions: Record<string, any> = {};
254+
const allTypes: Record<string, any> = {};
255+
const allVars: Record<string, any> = {};
256+
257+
Object.values(sharedUtilsModule.Packages).forEach((pkg: any) => {
258+
if (pkg.Functions) Object.assign(allFunctions, pkg.Functions);
259+
if (pkg.Types) Object.assign(allTypes, pkg.Types);
260+
if (pkg.Vars) Object.assign(allVars, pkg.Vars);
261+
});
252262

253263
// Verify functions are parsed
254-
const sharedUtilsFunctions = sharedUtilsModule.Packages.src.Functions;
255-
expect(sharedUtilsFunctions).toBeDefined();
256-
expect(sharedUtilsFunctions['formatMessage']).toBeDefined();
257-
expect(sharedUtilsFunctions['capitalize']).toBeDefined();
258-
expect(sharedUtilsFunctions['formatDate']).toBeDefined();
259-
expect(sharedUtilsFunctions['addDays']).toBeDefined();
264+
expect(allFunctions['formatMessage']).toBeDefined();
265+
expect(allFunctions['capitalize']).toBeDefined();
266+
expect(allFunctions['formatDate']).toBeDefined();
267+
expect(allFunctions['addDays']).toBeDefined();
260268

261269
// Verify types are parsed
262-
const sharedUtilsTypes = sharedUtilsModule.Packages.src.Types;
263-
expect(sharedUtilsTypes).toBeDefined();
264-
expect(sharedUtilsTypes['StringOptions']).toBeDefined();
270+
expect(allTypes['StringOptions']).toBeDefined();
265271

266272
// Verify variables are parsed
267-
const sharedUtilsVars = sharedUtilsModule.Packages.src.Vars;
268-
expect(sharedUtilsVars).toBeDefined();
269-
expect(sharedUtilsVars['SHARED_CONSTANTS']).toBeDefined();
273+
expect(allVars['SHARED_CONSTANTS']).toBeDefined();
270274

271275
// Verify api-server module
272276
const apiServerModule = result.Modules['@test/api-server'];
273277
expect(apiServerModule).toBeDefined();
274-
expect(apiServerModule.Packages.src.Types['ApiServer']).toBeDefined();
275-
expect(apiServerModule.Packages.src.Types['ServerConfig']).toBeDefined();
278+
279+
// Collect types from api-server packages
280+
const apiServerTypes: Record<string, any> = {};
281+
Object.values(apiServerModule.Packages).forEach((pkg: any) => {
282+
if (pkg.Types) Object.assign(apiServerTypes, pkg.Types);
283+
});
284+
285+
expect(apiServerTypes['ApiServer']).toBeDefined();
286+
expect(apiServerTypes['ServerConfig']).toBeDefined();
276287

277288
// Verify dependency graph includes cross-package dependencies
278289
expect(result.Graph).toBeDefined();
@@ -476,7 +487,14 @@ export class WebApplication {
476487

477488
// Verify inheritance is captured
478489
const uiModule = result.Modules['@test/ui-components'];
479-
const uiServiceType = uiModule.Packages.src.Types['UIService'];
490+
491+
// Collect types from all ui-components packages
492+
const uiTypes: Record<string, any> = {};
493+
Object.values(uiModule.Packages).forEach((pkg: any) => {
494+
if (pkg.Types) Object.assign(uiTypes, pkg.Types);
495+
});
496+
497+
const uiServiceType = uiTypes['UIService'];
480498
expect(uiServiceType).toBeDefined();
481499
expect(uiServiceType.Exported).toBe(true);
482500

@@ -668,18 +686,34 @@ export function createButton(props: ComponentProps): string {
668686
// Verify core module structure
669687
const coreModule = result.Modules['@test/core'];
670688
expect(coreModule).toBeDefined();
671-
expect(coreModule.Packages.src).toBeDefined();
672-
expect(coreModule.Packages.src.Types['Config']).toBeDefined();
673-
expect(coreModule.Packages.src.Types['BaseService']).toBeDefined();
674-
expect(coreModule.Packages.src.Functions['createConfig']).toBeDefined();
689+
690+
// Collect types and functions from all core packages
691+
const coreTypes: Record<string, any> = {};
692+
const coreFunctions: Record<string, any> = {};
693+
Object.values(coreModule.Packages).forEach((pkg: any) => {
694+
if (pkg.Types) Object.assign(coreTypes, pkg.Types);
695+
if (pkg.Functions) Object.assign(coreFunctions, pkg.Functions);
696+
});
697+
698+
expect(coreTypes['Config']).toBeDefined();
699+
expect(coreTypes['BaseService']).toBeDefined();
700+
expect(coreFunctions['createConfig']).toBeDefined();
675701

676702
// Verify UI module structure and dependencies
677703
const uiModule = result.Modules['@test/ui'];
678704
expect(uiModule).toBeDefined();
679-
expect(uiModule.Packages.src).toBeDefined();
680-
expect(uiModule.Packages.src.Types['UIService']).toBeDefined();
681-
expect(uiModule.Packages.src.Types['ComponentProps']).toBeDefined();
682-
expect(uiModule.Packages.src.Functions['createButton']).toBeDefined();
705+
706+
// Collect types and functions from all ui packages
707+
const uiTypes: Record<string, any> = {};
708+
const uiFunctions: Record<string, any> = {};
709+
Object.values(uiModule.Packages).forEach((pkg: any) => {
710+
if (pkg.Types) Object.assign(uiTypes, pkg.Types);
711+
if (pkg.Functions) Object.assign(uiFunctions, pkg.Functions);
712+
});
713+
714+
expect(uiTypes['UIService']).toBeDefined();
715+
expect(uiTypes['ComponentProps']).toBeDefined();
716+
expect(uiFunctions['createButton']).toBeDefined();
683717

684718
// Verify cross-module dependencies in graph
685719
const graphKeys = Object.keys(result.Graph);
@@ -936,12 +970,26 @@ export class UserService {
936970

937971
// Verify inheritance chains are captured
938972
const domainModule = result.Modules['@complex/domain'];
939-
const userType = domainModule.Packages.src.Types['User'];
973+
974+
// Collect types from all domain packages
975+
const domainTypes: Record<string, any> = {};
976+
Object.values(domainModule.Packages).forEach((pkg: any) => {
977+
if (pkg.Types) Object.assign(domainTypes, pkg.Types);
978+
});
979+
980+
const userType = domainTypes['User'];
940981
expect(userType).toBeDefined();
941982
expect(userType.Exported).toBe(true);
942983

943984
const serviceModule = result.Modules['@complex/service'];
944-
const userServiceType = serviceModule.Packages.src.Types['UserService'];
985+
986+
// Collect types from all service packages
987+
const serviceTypes: Record<string, any> = {};
988+
Object.values(serviceModule.Packages).forEach((pkg: any) => {
989+
if (pkg.Types) Object.assign(serviceTypes, pkg.Types);
990+
});
991+
992+
const userServiceType = serviceTypes['UserService'];
945993
expect(userServiceType).toBeDefined();
946994

947995
// Verify complex dependency graph
@@ -1218,7 +1266,7 @@ export const DEFAULT_VALUE = 'test';
12181266
.mockImplementation(() => {});
12191267

12201268
// Test without specifying monorepoMode (should default to combined)
1221-
const result = await parser.parseRepository(testProject.rootDir, {});
1269+
await parser.parseRepository(testProject.rootDir, {});
12221270

12231271
// Verify combined mode was called (default behavior)
12241272
expect(parseMonorepoCombinedModeSpy).toHaveBeenCalled();

ts-parser/src/types/typescript-mapping.md

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,25 @@
1212
- **Dir**: Relative path from repository root
1313
- **Dependencies**: From `package.json` dependencies/devDependencies
1414

15-
**Package (UNIAST) = TypeScript namespace/module**
16-
- **Directory**: Source directory with TypeScript files
17-
- **PkgPath**: Import path (e.g., "@myorg/mypackage/utils", "./src/models")
18-
- **IsMain**: True for main entry point (index.ts)
19-
- **IsTest**: True for test files (*.test.ts, *.spec.ts)
15+
**Package (UNIAST) = Individual TypeScript/JavaScript file**
16+
- **File-based**: Each TypeScript/JavaScript file is a separate Package
17+
- **PkgPath**: File path relative to module root (e.g., "src/utils.ts", "src/models/user.ts")
18+
- **IsMain**: True for main entry point files (index.ts, main.ts)
19+
- **IsTest**: True for test files (*.test.ts, *.spec.ts, files in test/ or __tests__/)
2020

2121
### 2. Mapping Strategy
2222

2323
```
2424
Repository (root)
2525
├── Module (npm package)
2626
│ ├── package.json
27-
│ ├── src/ (Package)
28-
│ │ ├── index.ts (IsMain=true)
29-
│ │ ├── utils.ts (PkgPath="./src/utils")
27+
│ ├── src/
28+
│ │ ├── index.ts (Package: PkgPath="src/index.ts", IsMain=true)
29+
│ │ ├── utils.ts (Package: PkgPath="src/utils.ts")
3030
│ │ └── models/
31-
│ │ └── user.ts (PkgPath="./src/models/user")
32-
│ └── test/ (Package)
33-
│ └── index.test.ts (IsTest=true)
31+
│ │ └── user.ts (Package: PkgPath="src/models/user.ts")
32+
│ └── test/
33+
│ └── index.test.ts (Package: PkgPath="test/index.test.ts", IsTest=true)
3434
└── node_modules/
3535
├── lodash (Module - external)
3636
│ └── ...
@@ -41,30 +41,34 @@ Repository (root)
4141
### 3. Package Path Resolution
4242

4343
**Internal Packages**:
44-
- Relative to module root
45-
- Examples: "./src/utils", "./lib/helpers", "./test/mocks"
44+
- File path relative to module root
45+
- Examples: "src/utils.ts", "lib/helpers.ts", "test/mocks.ts"
4646

4747
**External Packages**:
48-
- From node_modules
48+
- From node_modules, using module name
4949
- Examples: "lodash", "react", "@types/node"
5050

5151
### 4. File to Package Assignment
5252

53-
Each TypeScript file belongs to a Package based on:
54-
1. **Directory structure** - Files in same directory = same package
55-
2. **Import statements** - Used to determine package boundaries
56-
3. **package.json exports** - Define public API boundaries
53+
**One File = One Package**:
54+
1. **File-level granularity** - Each TypeScript/JavaScript file constitutes a separate Package
55+
2. **PkgPath** - File's relative path from module root (with forward slashes on all platforms)
56+
3. **No directory grouping** - Files are not grouped by directory into packages
57+
4. **Independent parsing** - Each file is parsed independently as its own Package
5758

5859
### 5. Special Cases
5960

6061
**Monorepos**:
6162
- Each workspace = separate Module
6263
- packages/app1, packages/lib1 = separate Modules
64+
- Each file within a workspace is a separate Package
6365

6466
**Scoped packages**:
6567
- @myorg/package1 = Module with name "@myorg/package1"
6668
- @myorg/package2 = separate Module
69+
- Files within scoped packages follow the same file-level Package rule
6770

6871
**TypeScript path mapping**:
69-
- tsconfig.json paths affect PkgPath resolution
70-
- "@/*": ["src/*"] → PkgPath="@/utils" maps to "./src/utils"
72+
- tsconfig.json paths affect module resolution during parsing
73+
- Actual PkgPath remains the physical file path relative to module root
74+
- Path aliases are resolved during symbol resolution, not for PkgPath naming

ts-parser/src/utils/parsing-strategy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ export class ParsingStrategySelector {
169169
tooManyFiles: totalFiles > this.THRESHOLDS.TOTAL_FILES_LARGE,
170170
tooLarge: totalSizeMB > this.THRESHOLDS.TOTAL_SIZE_LARGE_MB,
171171
tooManyPackages: packageCount > this.THRESHOLDS.PACKAGE_COUNT_LARGE,
172-
hasLargePackages: hasLargePackages,
172+
hasLargePackages,
173173
highAvgFiles: avgFilesPerPackage > this.THRESHOLDS.AVG_FILES_PER_PACKAGE,
174174
highMemoryUsage: estimatedMemoryUsageMB > this.THRESHOLDS.MEMORY_USAGE_LARGE_MB,
175175
};

0 commit comments

Comments
 (0)