@@ -132,7 +132,13 @@ class JSDocTypeExpr extends @jsdoc_type_expr, JSDocTypeExprParent, TypeAnnotatio
132132 }
133133
134134 override Stmt getEnclosingStmt ( ) {
135- result .getDocumentation ( ) = getJSDocComment ( )
135+ exists ( Documentable astNode | astNode .getDocumentation ( ) = getJSDocComment ( ) |
136+ result = astNode
137+ or
138+ result = astNode .( ExprOrType ) .getEnclosingStmt ( )
139+ or
140+ result = astNode .( Property ) .getObjectExpr ( ) .getEnclosingStmt ( )
141+ )
136142 }
137143
138144 override StmtContainer getContainer ( ) { result = getEnclosingStmt ( ) .getContainer ( ) }
@@ -205,7 +211,44 @@ class JSDocNamedTypeExpr extends @jsdoc_named_type_expr, JSDocTypeExpr {
205211
206212 override predicate isRawFunction ( ) { getName ( ) = "Function" }
207213
214+ /**
215+ * Holds if this name consists of the unqualified name `prefix`
216+ * followed by a (possibly empty) `suffix`.
217+ *
218+ * For example:
219+ * - `foo.bar.Baz` has prefix `foo` and suffix `.bar.Baz`.
220+ * - `Baz` has prefix `Baz` and an empty suffix.
221+ */
222+ predicate hasNameParts ( string prefix , string suffix ) {
223+ exists ( string regex , string name | regex = "([^.]+)(.*)" |
224+ name = getName ( ) and
225+ prefix = name .regexpCapture ( regex , 1 ) and
226+ suffix = name .regexpCapture ( regex , 2 )
227+ )
228+ }
229+
230+ pragma [ noinline]
231+ pragma [ nomagic]
232+ private predicate hasNamePartsAndEnv ( string prefix , string suffix , JSDoc:: Environment env ) {
233+ // Force join ordering
234+ hasNameParts ( prefix , suffix ) and
235+ env .isContainerInScope ( getContainer ( ) )
236+ }
237+
238+ /**
239+ * Gets the qualified name of this name by resolving its prefix, if any.
240+ */
241+ private string resolvedName ( ) {
242+ exists ( string prefix , string suffix , JSDoc:: Environment env |
243+ hasNamePartsAndEnv ( prefix , suffix , env ) and
244+ result = env .resolveAlias ( prefix ) + suffix
245+ )
246+ }
247+
208248 override predicate hasQualifiedName ( string globalName ) {
249+ globalName = resolvedName ( )
250+ or
251+ not exists ( resolvedName ( ) ) and
209252 globalName = getName ( )
210253 }
211254}
@@ -353,3 +396,65 @@ class JSDocError extends @jsdoc_error {
353396 /** Gets a textual representation of this element. */
354397 string toString ( ) { jsdoc_errors ( this , _, _, result ) }
355398}
399+
400+ module JSDoc {
401+ /**
402+ * A statement container which may declare JSDoc name aliases.
403+ */
404+ class Environment extends StmtContainer {
405+ /**
406+ * Gets the fully qualified name aliased by the given unqualified name
407+ * within this container.
408+ */
409+ string resolveAlias ( string alias ) {
410+ result = GlobalAccessPath:: getAccessPath ( getNodeFromAlias ( alias ) )
411+ }
412+
413+ /**
414+ * Gets a data flow node from which the type name `alias` should
415+ * be resolved.
416+ */
417+ DataFlow:: Node getNodeFromAlias ( string alias ) {
418+ isPrefixUsedInFile ( alias , getFile ( ) ) and // Restrict size of predicate
419+ exists ( VarDef def , VarRef ref |
420+ def .getContainer ( ) = this and
421+ ref = def .getTarget ( ) .( BindingPattern ) .getABindingVarRef ( ) and
422+ ref .getName ( ) = alias
423+ |
424+ exists ( PropertyPattern p |
425+ ref = p .getValuePattern ( ) and
426+ result .getAstNode ( ) = p
427+ )
428+ or
429+ result = DataFlow:: valueNode ( def .( Stmt ) )
430+ or
431+ ref = def .getTarget ( ) and
432+ result = def .getSource ( ) .flow ( )
433+ )
434+ }
435+
436+ /**
437+ * Holds if the alises declared in this environment should be in scope
438+ * within the given container.
439+ *
440+ * Specifically, this holds if this environment declares at least one
441+ * alias and is an ancestor of `container`.
442+ */
443+ final predicate isContainerInScope ( StmtContainer container ) {
444+ exists ( resolveAlias ( _) ) and // restrict size of predicate
445+ container = this
446+ or
447+ isContainerInScope ( container .getEnclosingContainer ( ) )
448+ }
449+ }
450+
451+ /**
452+ * Holds if a JSDoc type in `f` referenced the given local name.
453+ */
454+ private predicate isPrefixUsedInFile ( string name , File f ) {
455+ exists ( JSDocNamedTypeExpr t |
456+ t .hasNameParts ( name , _) and
457+ t .getFile ( ) = f
458+ )
459+ }
460+ }
0 commit comments