Skip to content

Commit ade9ba7

Browse files
alan-agius4thePunderWoman
authored andcommitted
refactor(compiler-cli): Improved filterMethodOverloads to Include Members without body (angular#58445)
Previously, `filterMethodOverloads` excluded all members without a body, causing issues with the extraction of functions and members in TypeScript types. PR Close angular#58445
1 parent 3ecf0f6 commit ade9ba7

File tree

2 files changed

+38
-21
lines changed

2 files changed

+38
-21
lines changed

packages/compiler-cli/src/ngtsc/docs/src/class_extractor.ts

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -271,26 +271,21 @@ class ClassExtractor {
271271
/** The result only contains properties, method implementations and abstracts */
272272
private filterMethodOverloads(declarations: ts.Declaration[]): ts.Declaration[] {
273273
return declarations.filter((declaration, index) => {
274+
// Check if the declaration is a function or method
274275
if (ts.isFunctionDeclaration(declaration) || ts.isMethodDeclaration(declaration)) {
275-
if (ts.getCombinedModifierFlags(declaration) & ts.ModifierFlags.Abstract) {
276-
// TS enforces that all declarations of an abstract method are consecutive
277-
const previousDeclaration = declarations[index - 1];
278-
279-
const samePreviousAbstractMethod =
280-
previousDeclaration &&
281-
ts.isMethodDeclaration(previousDeclaration) &&
282-
ts.getCombinedModifierFlags(previousDeclaration) & ts.ModifierFlags.Abstract &&
283-
previousDeclaration.name.getText() === declaration.name?.getText();
284-
285-
// We just need a reference to one member
286-
// In the case of Abstract Methods we only want to return the first abstract.
287-
// Others with the same name are considered as overloads
288-
// Later on, the function extractor will handle overloads and implementation detection
289-
return !samePreviousAbstractMethod;
290-
}
291-
292-
return !!declaration.body;
276+
// TypeScript ensures that all declarations for a given abstract method appear consecutively.
277+
const nextDeclaration = declarations[index + 1];
278+
const isNextAbstractMethodWithSameName =
279+
nextDeclaration &&
280+
ts.isMethodDeclaration(nextDeclaration) &&
281+
nextDeclaration.name.getText() === declaration.name?.getText();
282+
283+
// Return only the last occurrence of an abstract method to avoid overload duplication.
284+
// Subsequent overloads or implementations are handled separately by the function extractor.
285+
return !isNextAbstractMethodWithSameName;
293286
}
287+
288+
// Include non-method declarations, such as properties, without filtering.
294289
return true;
295290
});
296291
}

packages/compiler-cli/test/ngtsc/doc_extraction/class_doc_extraction_spec.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -276,15 +276,15 @@ runInEachFileSystem(() => {
276276
eyeColor = 'brown';
277277
278278
/** @internal */
279-
uuid: string;
280-
279+
uuid: string;
280+
281281
// @internal
282282
foreignId: string;
283283
284284
/** @internal */
285285
_doSomething() {}
286286
287-
// @internal
287+
// @internal
288288
_doSomethingElse() {}
289289
}
290290
`,
@@ -662,5 +662,27 @@ runInEachFileSystem(() => {
662662
expect(fooEntry.memberType).toBe(MemberType.Property);
663663
expect((fooEntry as PropertyEntry).type).toBe('string');
664664
});
665+
666+
it('should extract members of a class from .d.ts', () => {
667+
env.write(
668+
'index.d.ts',
669+
`
670+
export declare class UserProfile {
671+
firstName: string;
672+
save(): void;
673+
}`,
674+
);
675+
676+
const docs: DocEntry[] = env.driveDocsExtraction('index.d.ts');
677+
expect(docs.length).toBe(1);
678+
679+
const classEntry = docs[0] as ClassEntry;
680+
expect(classEntry.members.length).toBe(2);
681+
682+
const [firstNameEntry, saveEntry] = classEntry.members;
683+
684+
expect(firstNameEntry.name).toBe('firstName');
685+
expect(saveEntry.name).toBe('save');
686+
});
665687
});
666688
});

0 commit comments

Comments
 (0)