@@ -21,6 +21,12 @@ type DeDupeMember =
2121 | d . ComponentCompilerEvent
2222 | d . ComponentCompilerChangeHandler ;
2323
24+ type DependentClass = {
25+ classNode : ts . ClassDeclaration ;
26+ sourceFile : ts . SourceFile ;
27+ fileName : string ;
28+ } ;
29+
2430/**
2531 * Given two arrays of static members, return a new array containing only the
2632 * members from the first array that are not present in the second array.
@@ -42,6 +48,105 @@ const deDupeMembers = <T extends DeDupeMember>(dedupeMembers: T[], staticMembers
4248 ) ;
4349} ;
4450
51+ /**
52+ * Helper function to resolve and process an extended class from a module.
53+ * This handles:
54+ * 1. Resolving the module path
55+ * 2. Getting the source file
56+ * 3. Finding the class declaration
57+ * 4. Adding to dependent classes tree
58+ *
59+ * @param compilerCtx
60+ * @param buildCtx
61+ * @param classDeclaration the current class being analyzed
62+ * @param currentSource the source file of the current class
63+ * @param moduleSpecifier the module path to resolve
64+ * @param className the name of the class to find in the resolved module
65+ * @param dependentClasses the array to add found classes to
66+ * @param keepLooking whether to continue recursively looking for more extended classes
67+ * @param typeChecker
68+ * @param ogModule
69+ * @returns the found class declaration or undefined
70+ */
71+ function resolveAndProcessExtendedClass (
72+ compilerCtx : d . CompilerCtx ,
73+ buildCtx : d . BuildCtx ,
74+ classDeclaration : ts . ClassDeclaration ,
75+ currentSource : ts . SourceFile ,
76+ moduleSpecifier : string ,
77+ className : string ,
78+ dependentClasses : DependentClass [ ] ,
79+ keepLooking : boolean ,
80+ typeChecker : ts . TypeChecker ,
81+ ogModule : d . Module ,
82+ ) : ts . ClassDeclaration | undefined {
83+ const foundFile = tsResolveModuleName ( buildCtx . config , compilerCtx , moduleSpecifier , currentSource . fileName ) ;
84+
85+ if ( ! foundFile ?. resolvedModule || ! className ) {
86+ return undefined ;
87+ }
88+
89+ // 1) resolve the module name to a file
90+ let foundSource : ts . SourceFile = compilerCtx . moduleMap . get (
91+ foundFile . resolvedModule . resolvedFileName ,
92+ ) ?. staticSourceFile ;
93+
94+ if ( ! foundSource ) {
95+ // Stencil only loads full-fledged component modules from node_modules collections,
96+ // so if we didn't find the source file in the module map,
97+ // let's create a temporary program and get the source file from there
98+ foundSource = tsGetSourceFile ( buildCtx . config , foundFile ) ;
99+
100+ if ( ! foundSource ) {
101+ // ts could not resolve the module. Likely because `allowJs` is not set to `true`
102+ const err = buildWarn ( buildCtx . diagnostics ) ;
103+ err . messageText = `Unable to resolve import "${ moduleSpecifier } " from "${ currentSource . fileName } ".
104+ This can happen when trying to resolve .js files and "allowJs" is not set to "true" in your tsconfig.json.` ;
105+ if ( ! buildCtx . config . _isTesting ) augmentDiagnosticWithNode ( err , classDeclaration ) ;
106+ return undefined ;
107+ }
108+ }
109+
110+ // 2) get the exported declaration from the module
111+ const matchedStatement = foundSource . statements . find ( matchesNamedDeclaration ( className ) ) ;
112+ if ( ! matchedStatement ) {
113+ // we couldn't find the imported declaration as an exported statement in the module
114+ const err = buildWarn ( buildCtx . diagnostics ) ;
115+ err . messageText = `Unable to find "${ className } " in the imported module "${ moduleSpecifier } ".
116+ Please import class / mixin-factory declarations directly and not via barrel files.` ;
117+ if ( ! buildCtx . config . _isTesting ) augmentDiagnosticWithNode ( err , classDeclaration ) ;
118+ return undefined ;
119+ }
120+
121+ let foundClassDeclaration = matchedStatement
122+ ? ts . isClassDeclaration ( matchedStatement )
123+ ? matchedStatement
124+ : undefined
125+ : undefined ;
126+
127+ if ( ! foundClassDeclaration && matchedStatement ) {
128+ // the found `extends` type does not resolve to a class declaration;
129+ // if it's wrapped in a function - let's try and find it inside
130+ foundClassDeclaration = findClassWalk ( matchedStatement ) ;
131+ keepLooking = false ;
132+ }
133+
134+ if ( foundClassDeclaration && ! dependentClasses . some ( ( dc ) => dc . classNode === foundClassDeclaration ) ) {
135+ // 3) if we found the class declaration, push it and check if it itself extends from another class
136+ dependentClasses . push ( {
137+ classNode : foundClassDeclaration ,
138+ sourceFile : foundSource ,
139+ fileName : foundFile . resolvedModule . resolvedFileName ,
140+ } ) ;
141+
142+ if ( keepLooking ) {
143+ buildExtendsTree ( compilerCtx , foundClassDeclaration , dependentClasses , typeChecker , buildCtx , ogModule ) ;
144+ }
145+ }
146+
147+ return foundClassDeclaration ;
148+ }
149+
45150/**
46151 * A recursive function that walks the AST to find a class declaration.
47152 * @param node the current AST node
@@ -53,6 +158,21 @@ function findClassWalk(node?: ts.Node, name?: string): ts.ClassDeclaration | und
53158 if ( ! node ) return undefined ;
54159 if ( node && ts . isClassDeclaration ( node ) && ( ! name || node . name ?. text === name ) ) {
55160 return node ;
161+ } else if (
162+ node &&
163+ ts . isVariableDeclaration ( node ) &&
164+ // @ts -ignore
165+ ( ! name || name === ( node . name ?. text || node . name ?. escapedText ) ) &&
166+ node . initializer &&
167+ ts . isArrowFunction ( node . initializer )
168+ ) {
169+ // handle case where class is wrapped in a mixin factory function
170+ let found : ts . ClassDeclaration | undefined ;
171+ ts . forEachChild ( node . initializer . body , ( child ) => {
172+ if ( found ) return ;
173+ if ( ts . isClassDeclaration ( child ) ) found = child ;
174+ } ) ;
175+ return found ;
56176 }
57177 let found : ts . ClassDeclaration | undefined ;
58178
@@ -109,7 +229,7 @@ function matchesNamedDeclaration(name: string) {
109229function buildExtendsTree (
110230 compilerCtx : d . CompilerCtx ,
111231 classDeclaration : ts . ClassDeclaration ,
112- dependentClasses : { classNode : ts . ClassDeclaration ; sourceFile : ts . SourceFile ; fileName : string } [ ] ,
232+ dependentClasses : DependentClass [ ] ,
113233 typeChecker : ts . TypeChecker ,
114234 buildCtx : d . BuildCtx ,
115235 ogModule : d . Module ,
@@ -153,6 +273,9 @@ function buildExtendsTree(
153273 // if it's wrapped in a function - let's try and find it inside
154274 const node = aliasedSymbol ?. declarations ?. [ 0 ] ;
155275 foundClassDeclaration = findClassWalk ( node ) ;
276+ if ( ! node ) {
277+ throw 'revert to sad path' ;
278+ }
156279 keepLooking = false ;
157280 }
158281
@@ -227,83 +350,57 @@ function buildExtendsTree(
227350 if ( element . name . getText ( ) === extendee . getText ( ) ) {
228351 // 3) check the name matches the `extends` type expression
229352 const className = element . propertyName ?. getText ( ) || element . name . getText ( ) ;
230- const foundFile = tsResolveModuleName (
231- buildCtx . config ,
353+ const moduleSpecifier = statement . moduleSpecifier . getText ( ) . replaceAll ( / [ ' " ] / g, '' ) ;
354+
355+ resolveAndProcessExtendedClass (
232356 compilerCtx ,
233- statement . moduleSpecifier . getText ( ) . replaceAll ( / [ ' " ] / g, '' ) ,
234- currentSource . fileName ,
357+ buildCtx ,
358+ classDeclaration ,
359+ currentSource ,
360+ moduleSpecifier ,
361+ className ,
362+ dependentClasses ,
363+ keepLooking ,
364+ typeChecker ,
365+ ogModule ,
235366 ) ;
236-
237- if ( foundFile ?. resolvedModule && className ) {
238- // 4) resolve the module name to a file
239- let foundSource : ts . SourceFile = compilerCtx . moduleMap . get (
240- foundFile . resolvedModule . resolvedFileName ,
241- ) ?. staticSourceFile ;
242-
243- if ( ! foundSource ) {
244- // Stencil only loads full-fledged component modules from node_modules collections,
245- // so if we didn't find the source file in the module map,
246- // let's create a temporary program and get the source file from there
247- foundSource = tsGetSourceFile ( buildCtx . config , foundFile ) ;
248-
249- if ( ! foundSource ) {
250- // ts could not resolve the module. Likely because `allowJs` is not set to `true`
251- const err = buildWarn ( buildCtx . diagnostics ) ;
252- err . messageText = `Unable to resolve import "${ statement . moduleSpecifier . getText ( ) } " from "${ currentSource . fileName } ".
253- This can happen when trying to resolve .js files and "allowJs" is not set to "true" in your tsconfig.json.` ;
254- if ( ! buildCtx . config . _isTesting ) augmentDiagnosticWithNode ( err , classDeclaration ) ;
255- return ;
256- }
257- }
258-
259- const matchedStatement = foundSource . statements . find ( matchesNamedDeclaration ( className ) ) ;
260- if ( ! matchedStatement ) {
261- // we couldn't find the imported declaration as an exported statement in the module
262- const err = buildWarn ( buildCtx . diagnostics ) ;
263- err . messageText = `Unable to find "${ className } " in the imported module "${ statement . moduleSpecifier . getText ( ) } ".
264- Please import class / mixin-factory declarations directly and not via barrel files.` ;
265- if ( ! buildCtx . config . _isTesting ) augmentDiagnosticWithNode ( err , classDeclaration ) ;
266- return ;
267- }
268-
269- foundClassDeclaration = matchedStatement
270- ? ts . isClassDeclaration ( matchedStatement )
271- ? matchedStatement
272- : undefined
273- : undefined ;
274-
275- if ( ! foundClassDeclaration && matchedStatement ) {
276- // 5.b) the found `extends` type does not resolve to a class declaration;
277- // if it's wrapped in a function - let's try and find it inside
278- foundClassDeclaration = findClassWalk ( matchedStatement ) ;
279- keepLooking = false ;
280- }
281-
282- if ( foundClassDeclaration && ! dependentClasses . some ( ( dc ) => dc . classNode === foundClassDeclaration ) ) {
283- // 6) if we found the class declaration, push it and check if it itself extends from another class
284- dependentClasses . push ( {
285- classNode : foundClassDeclaration ,
286- sourceFile : foundSource ,
287- fileName : currentSource . fileName ,
288- } ) ;
289-
290- if ( keepLooking ) {
291- buildExtendsTree (
292- compilerCtx ,
293- foundClassDeclaration ,
294- dependentClasses ,
295- typeChecker ,
296- buildCtx ,
297- ogModule ,
298- ) ;
299- }
300- return ;
301- }
302- }
303367 }
304368 } ) ;
305369 }
306370 } ) ;
371+
372+ if ( ! importStatements . length ) {
373+ // we're in a cjs module (probably in a Jest test) - loop through require modules statements
374+ const requireStatements = currentSource . statements . filter ( ts . isVariableStatement ) ;
375+ requireStatements . forEach ( ( statement ) => {
376+ statement . declarationList . declarations . forEach ( ( declaration ) => {
377+ if (
378+ declaration . initializer &&
379+ ts . isCallExpression ( declaration . initializer ) &&
380+ ts . isIdentifier ( declaration . initializer . expression ) &&
381+ declaration . initializer . expression . escapedText === 'require' &&
382+ declaration . initializer . arguments . length === 1 &&
383+ ts . isStringLiteral ( declaration . initializer . arguments [ 0 ] )
384+ ) {
385+ const moduleSpecifier = declaration . initializer . arguments [ 0 ] . text . replaceAll ( / [ ' " ] / g, '' ) ;
386+ const className = extendee . getText ( ) ;
387+
388+ resolveAndProcessExtendedClass (
389+ compilerCtx ,
390+ buildCtx ,
391+ classDeclaration ,
392+ currentSource ,
393+ moduleSpecifier ,
394+ className ,
395+ dependentClasses ,
396+ keepLooking ,
397+ typeChecker ,
398+ ogModule ,
399+ ) ;
400+ }
401+ } ) ;
402+ } ) ;
403+ }
307404 }
308405 } ) ;
309406
0 commit comments