@@ -23,8 +23,8 @@ import javascript
2323 * where the first argument is the module name, the second argument an
2424 * array of dependencies, and the third argument a factory method or object.
2525 */
26- class AMDModuleDefinition extends CallExpr {
27- AMDModuleDefinition ( ) {
26+ class AmdModuleDefinition extends CallExpr {
27+ AmdModuleDefinition ( ) {
2828 getParent ( ) instanceof ExprStmt and
2929 getCallee ( ) .( GlobalVarAccess ) .getName ( ) = "define" and
3030 exists ( int n | n = getNumArgument ( ) |
@@ -153,7 +153,7 @@ class AMDModuleDefinition extends CallExpr {
153153 result = getModuleExpr ( ) .analyze ( ) .getAValue ( )
154154 or
155155 // explicit exports: anything assigned to `module.exports`
156- exists ( AbstractProperty moduleExports , AMDModule m |
156+ exists ( AbstractProperty moduleExports , AmdModule m |
157157 this = m .getDefine ( ) and
158158 moduleExports .getBase ( ) .( AbstractModuleObject ) .getModule ( ) = m and
159159 moduleExports .getPropertyName ( ) = "exports"
@@ -170,10 +170,15 @@ class AMDModuleDefinition extends CallExpr {
170170 }
171171}
172172
173+ /**
174+ * DEPRECATED: Use `AmdModuleDefinition` instead.
175+ */
176+ deprecated class AMDModuleDefinition = AmdModuleDefinition ;
177+
173178/** An AMD dependency, considered as a path expression. */
174179private class AmdDependencyPath extends PathExprCandidate {
175180 AmdDependencyPath ( ) {
176- exists ( AMDModuleDefinition amd |
181+ exists ( AmdModuleDefinition amd |
177182 this = amd .getDependencies ( ) .getAnElement ( ) or
178183 this = amd .getARequireCall ( ) .getAnArgument ( )
179184 )
@@ -191,21 +196,83 @@ private class ConstantAmdDependencyPathElement extends PathExprInModule, Constan
191196 * Holds if `def` is an AMD module definition in `tl` which is not
192197 * nested inside another module definition.
193198 */
194- private predicate amdModuleTopLevel ( AMDModuleDefinition def , TopLevel tl ) {
199+ private predicate amdModuleTopLevel ( AmdModuleDefinition def , TopLevel tl ) {
195200 def .getTopLevel ( ) = tl and
196- not def .getParent + ( ) instanceof AMDModuleDefinition
201+ not def .getParent + ( ) instanceof AmdModuleDefinition
202+ }
203+
204+ /**
205+ * An AMD dependency, viewed as an import.
206+ */
207+ private class AmdDependencyImport extends Import {
208+ AmdDependencyImport ( ) { this = any ( AmdModuleDefinition def ) .getADependency ( ) }
209+
210+ override Module getEnclosingModule ( ) { this = result .( AmdModule ) .getDefine ( ) .getADependency ( ) }
211+
212+ override PathExpr getImportedPath ( ) { result = this }
213+
214+ /**
215+ * Gets a file that looks like it might be the target of this import.
216+ *
217+ * Specifically, we look for files whose absolute path ends with the imported path, possibly
218+ * adding well-known JavaScript file extensions like `.js`.
219+ */
220+ private File guessTarget ( ) {
221+ exists ( PathString imported , string abspath , string dirname , string basename |
222+ targetCandidate ( result , abspath , imported , dirname , basename )
223+ |
224+ abspath .regexpMatch ( ".*/\\Q" + imported + "\\E" )
225+ or
226+ exists ( Folder dir |
227+ // `dir` ends with the dirname of the imported path
228+ dir .getAbsolutePath ( ) .regexpMatch ( ".*/\\Q" + dirname + "\\E" ) or
229+ dirname = ""
230+ |
231+ result = dir .getJavaScriptFile ( basename )
232+ )
233+ )
234+ }
235+
236+ /**
237+ * Holds if `f` is a file whose stem (that is, basename without extension) matches the imported path.
238+ *
239+ * Additionally, `abspath` is bound to the absolute path of `f`, `imported` to the imported path, and
240+ * `dirname` and `basename` to the dirname and basename (respectively) of `imported`.
241+ */
242+ private predicate targetCandidate (
243+ File f , string abspath , PathString imported , string dirname , string basename
244+ ) {
245+ imported = getImportedPath ( ) .getValue ( ) and
246+ f .getStem ( ) = imported .getStem ( ) and
247+ f .getAbsolutePath ( ) = abspath and
248+ dirname = imported .getDirName ( ) and
249+ basename = imported .getBaseName ( )
250+ }
251+
252+ /**
253+ * Gets the module whose absolute path matches this import, if there is only a single such module.
254+ */
255+ private Module resolveByAbsolutePath ( ) {
256+ count ( guessTarget ( ) ) = 1 and
257+ result .getFile ( ) = guessTarget ( )
258+ }
259+
260+ override Module getImportedModule ( ) {
261+ result = super .getImportedModule ( )
262+ or
263+ not exists ( super .getImportedModule ( ) ) and
264+ result = resolveByAbsolutePath ( )
265+ }
197266}
198267
199268/**
200269 * An AMD-style module.
201270 */
202- class AMDModule extends Module {
203- AMDModule ( ) { strictcount ( AMDModuleDefinition def | amdModuleTopLevel ( def , this ) ) = 1 }
271+ class AmdModule extends Module {
272+ AmdModule ( ) { strictcount ( AmdModuleDefinition def | amdModuleTopLevel ( def , this ) ) = 1 }
204273
205274 /** Gets the definition of this module. */
206- AMDModuleDefinition getDefine ( ) { amdModuleTopLevel ( result , this ) }
207-
208- override Module getAnImportedModule ( ) { result .getFile ( ) = resolve ( getDefine ( ) .getADependency ( ) ) }
275+ AmdModuleDefinition getDefine ( ) { amdModuleTopLevel ( result , this ) }
209276
210277 override predicate exports ( string name , ASTNode export ) {
211278 exists ( DataFlow:: PropWrite pwn | export = pwn .getAstNode ( ) |
@@ -214,3 +281,8 @@ class AMDModule extends Module {
214281 )
215282 }
216283}
284+
285+ /**
286+ * DEPRECATED: Use `AmdModule` instead.
287+ */
288+ deprecated class AMDModule = AmdModule ;
0 commit comments