diff --git a/src/analyze/stages/flavor/visit-inheritance.ts b/src/analyze/stages/flavor/visit-inheritance.ts index 24d87ca1..ba96f0d4 100644 --- a/src/analyze/stages/flavor/visit-inheritance.ts +++ b/src/analyze/stages/flavor/visit-inheritance.ts @@ -1,17 +1,26 @@ import { Node } from "typescript"; -import { InheritanceTreeClause, InheritanceTreeNode } from "../../types/inheritance-tree"; import { AnalyzerVisitContext } from "../../analyzer-visit-context"; +import { InheritanceTreeClause, InheritanceTreeNode } from "../../types/inheritance-tree"; import { executeFunctionsUntilMatch } from "../../util/execute-functions-until-match"; -export function visitAndExpandInheritClause(inheritClause: InheritanceTreeClause, context: AnalyzerVisitContext): InheritanceTreeClause { +export function visitAndExpandInheritClause( + inheritClause: InheritanceTreeClause, + context: AnalyzerVisitContext, + visitSet: Set +): InheritanceTreeClause { const resolved = (() => { if (inheritClause.resolved == null) return undefined; return inheritClause.resolved.map(resolved => { let inheritance: InheritanceTreeClause[] = []; - visitInheritance(resolved.node, context, results => { - inheritance = inheritance.concat(results); - }); + visitInheritance( + resolved.node, + context, + results => { + inheritance = inheritance.concat(results); + }, + visitSet + ); return { ...resolved, @@ -22,22 +31,30 @@ export function visitAndExpandInheritClause(inheritClause: InheritanceTreeClause return { ...inheritClause, - horizontalInherits: inheritClause.horizontalInherits?.map(arg => visitAndExpandInheritClause(arg, context)), + horizontalInherits: inheritClause.horizontalInherits?.map(arg => visitAndExpandInheritClause(arg, context, visitSet)), resolved }; } -export function visitInheritance(node: Node, context: AnalyzerVisitContext, emit: (results: InheritanceTreeClause[]) => void): void { +export function visitInheritance( + node: Node, + context: AnalyzerVisitContext, + emit: (results: InheritanceTreeClause[]) => void, + visitSet?: Set +): void { + visitSet = visitSet || new Set(); + + if (visitSet.has(node)) { + return; + } + + visitSet.add(node); + const result = executeFunctionsUntilMatch(context.flavors, "discoverInheritance", node, context); if (result != null) { - emit(result.value.map(link => visitAndExpandInheritClause(link, context))); + emit(result.value.map(link => visitAndExpandInheritClause(link, context, visitSet!))); if (!result.shouldContinue) return; } - - // Visit child nodes - /*node.forEachChild(child => { - visitInheritance2(child, context, emit); - });*/ } diff --git a/test/flavors/custom-element/mixin-test.ts b/test/flavors/custom-element/mixin-test.ts index eb5326c4..f2745540 100644 --- a/test/flavors/custom-element/mixin-test.ts +++ b/test/flavors/custom-element/mixin-test.ts @@ -2,6 +2,55 @@ import test from "ava"; import { analyzeText } from "../../../src/analyze/analyze-text"; import { getAttributeNames, getComponentProp, getPropertyNames } from "../../helpers/util"; +test("Handles circular inheritance", t => { + const { result } = analyzeText(` + class MyElement extends MyElement { + } + + /** + * @element + */ + class MyElement extends MyBase { + static get observedAttributes() { + return ["a", "b"]; + } + } + `); + + const { members } = result.componentDefinitions[0]?.declaration(); + + const attributeNames = getAttributeNames(members); + + t.deepEqual(attributeNames, ["a", "b"]); +}); + +test("Handles circular inheritance using mixins", t => { + const { result } = analyzeText(` + const Mixin1 = (Base) => { + return class Mixin1 extends Mixin2(Base) {} + } + + const Mixin2 = (Base) => { + return class Mixin2 extends Mixin1(Base) {} + } + + /** + * @element + */ + class MyElement extends Mixin1(Mixin2(HTMLElement)) { + static get observedAttributes() { + return ["a", "b"]; + } + } + `); + + const { members } = result.componentDefinitions[0]?.declaration(); + + const attributeNames = getAttributeNames(members); + + t.deepEqual(attributeNames, ["a", "b"]); +}); + test("Handles simple mixin", t => { const { result } = analyzeText(` const MyMixin = (Base) => {