@@ -85,6 +85,12 @@ public class CheckMissingRequires extends AbstractModuleCallback implements Comp
85
85
"''{0}'' references a namespace which was not required by this file.\n "
86
86
+ "Please add a goog.requireType." );
87
87
88
+ public static final DiagnosticType NON_LEGACY_GOOG_MODULE_REFERENCE =
89
+ DiagnosticType .error (
90
+ "JSC_NON_LEGACY_GOOG_MODULE_REFERENCE" ,
91
+ "''{0}'' references the name of a module without goog.declareLegacyNamespace(), which "
92
+ + "is not actually defined. Use goog.module.get() instead." );
93
+
88
94
/** The set of template parameter names found so far in the file currently being checked. */
89
95
private final LinkedHashSet <String > templateParamNames = new LinkedHashSet <>();
90
96
@@ -133,7 +139,7 @@ private void visitNode(NodeTraversal t, Node n, ModuleMetadata currentModule) {
133
139
if (root .equals ("this" ) || root .equals ("super" )) {
134
140
return ;
135
141
}
136
- visitQualifiedName (t , n , currentModule , qualifiedName , /* isStrongReference= */ true );
142
+ visitQualifiedName (t , n , currentModule , qualifiedName , Strength . CODE );
137
143
}
138
144
139
145
if (n .isName () && !n .getString ().isEmpty ()) {
@@ -147,48 +153,47 @@ private void visitJsDocInfo(NodeTraversal t, ModuleMetadata currentModule, JSDoc
147
153
templateParamNames .addAll (info .getTemplateTypeNames ());
148
154
templateParamNames .addAll (info .getTypeTransformations ().keySet ());
149
155
if (info .hasType ()) {
150
- visitJsDocExpr (t , currentModule , info .getType (), /* isStrongReference= */ false );
156
+ visitJsDocExpr (t , currentModule , info .getType (), Strength . WEAK_TYPE );
151
157
}
152
158
for (String param : info .getParameterNames ()) {
153
159
if (info .hasParameterType (param )) {
154
- visitJsDocExpr (
155
- t , currentModule , info .getParameterType (param ), /* isStrongReference= */ false );
160
+ visitJsDocExpr (t , currentModule , info .getParameterType (param ), Strength .WEAK_TYPE );
156
161
}
157
162
}
158
163
if (info .hasReturnType ()) {
159
- visitJsDocExpr (t , currentModule , info .getReturnType (), /* isStrongReference= */ false );
164
+ visitJsDocExpr (t , currentModule , info .getReturnType (), Strength . WEAK_TYPE );
160
165
}
161
166
if (info .hasEnumParameterType ()) {
162
- visitJsDocExpr (t , currentModule , info .getEnumParameterType (), /* isStrongReference= */ false );
167
+ visitJsDocExpr (t , currentModule , info .getEnumParameterType (), Strength . WEAK_TYPE );
163
168
}
164
169
if (info .hasTypedefType ()) {
165
- visitJsDocExpr (t , currentModule , info .getTypedefType (), /* isStrongReference= */ false );
170
+ visitJsDocExpr (t , currentModule , info .getTypedefType (), Strength . WEAK_TYPE );
166
171
}
167
172
if (info .hasThisType ()) {
168
- visitJsDocExpr (t , currentModule , info .getThisType (), /* isStrongReference= */ false );
173
+ visitJsDocExpr (t , currentModule , info .getThisType (), Strength . WEAK_TYPE );
169
174
}
170
175
if (info .hasBaseType ()) {
171
176
// Note that `@extends` requires a goog.require, not a goog.requireType.
172
- visitJsDocExpr (t , currentModule , info .getBaseType (), /* isStrongReference= */ true );
177
+ visitJsDocExpr (t , currentModule , info .getBaseType (), Strength . IMPLEMENTS_EXTENDS );
173
178
}
174
179
for (JSTypeExpression expr : info .getExtendedInterfaces ()) {
175
180
// Note that `@extends` requires a goog.require, not a goog.requireType.
176
- visitJsDocExpr (t , currentModule , expr , /* isStrongReference= */ true );
181
+ visitJsDocExpr (t , currentModule , expr , Strength . IMPLEMENTS_EXTENDS );
177
182
}
178
183
for (JSTypeExpression expr : info .getImplementedInterfaces ()) {
179
184
// Note that `@implements` requires a goog.require, not a goog.requireType.
180
- visitJsDocExpr (t , currentModule , expr , /* isStrongReference= */ true );
185
+ visitJsDocExpr (t , currentModule , expr , Strength . IMPLEMENTS_EXTENDS );
181
186
}
182
187
}
183
188
184
189
private void visitJsDocExpr (
185
190
NodeTraversal t ,
186
191
ModuleMetadata currentModule ,
187
192
JSTypeExpression expr ,
188
- boolean isStrongReference ) {
193
+ Strength referenceStrength ) {
189
194
for (Node typeNode : expr .getAllTypeNodes ()) {
190
195
visitQualifiedName (
191
- t , typeNode , currentModule , QualifiedName .of (typeNode .getString ()), isStrongReference );
196
+ t , typeNode , currentModule , QualifiedName .of (typeNode .getString ()), referenceStrength );
192
197
}
193
198
}
194
199
@@ -258,7 +263,10 @@ private void visitMaybeDeclaration(NodeTraversal t, Node n, ModuleMetadata curre
258
263
// through namespace destructuring, otherwise...
259
264
if (alternateFile != null && alternateFile .hasLegacyGoogNamespaces ()) {
260
265
if (!hasAcceptableRequire (
261
- currentFile , qualifiedName , alternateFile , require .isStrongRequire ())) {
266
+ currentFile ,
267
+ qualifiedName ,
268
+ alternateFile ,
269
+ require .isStrongRequire () ? Strength .CODE : Strength .WEAK_TYPE )) {
262
270
// TODO: report on the node that needs to be removed, include the namespace that needs
263
271
// to be added.
264
272
final DiagnosticType toReport =
@@ -281,7 +289,7 @@ private void visitQualifiedName(
281
289
Node n ,
282
290
ModuleMetadata currentFile ,
283
291
QualifiedName qualifiedName ,
284
- boolean isStrongReference ) {
292
+ Strength referenceStrength ) {
285
293
286
294
String rootName = qualifiedName .getRoot ();
287
295
if (qualifiedName .isSimple ()) {
@@ -301,10 +309,10 @@ private void visitQualifiedName(
301
309
302
310
Var var = t .getScope ().getVar (rootName );
303
311
if (var != null && var .getScope ().isLocal ()) {
304
- checkMissingRequireThroughShortName (t , n , var , currentFile , qualifiedName , isStrongReference );
312
+ checkMissingRequireThroughShortName (t , n , var , currentFile , qualifiedName , referenceStrength );
305
313
} else {
306
314
checkMissingRequireThroughFullyQualifiedName (
307
- t , n , currentFile , qualifiedName , isStrongReference );
315
+ t , n , currentFile , qualifiedName , referenceStrength );
308
316
}
309
317
}
310
318
@@ -325,14 +333,15 @@ private void checkMissingRequireThroughShortName(
325
333
Var localVar ,
326
334
ModuleMetadata currentFile ,
327
335
QualifiedName qualifiedName ,
328
- boolean isStrongReference ) {
336
+ Strength referenceStrength ) {
329
337
// TODO(b/333952917): Remove this once tsickle is fixed.
330
338
if (isTypeScriptSource (n )) {
331
339
return ;
332
340
}
333
341
334
342
if (!currentFile .isModule ()) {
335
343
// Don't worry about aliases outside of module files for now.
344
+ // TODO - b/390433455: also check aliases within the top level of a goog.scope file.
336
345
return ;
337
346
}
338
347
@@ -392,14 +401,14 @@ private void checkMissingRequireThroughShortName(
392
401
// fix
393
402
// it up. Write a different message if the namespace is already imported vs missing.
394
403
395
- if (!hasAcceptableRequire (currentFile , subName , requiredFile , isStrongReference )
404
+ if (!hasAcceptableRequire (currentFile , subName , requiredFile , referenceStrength )
396
405
|| originalRequiredFile != requiredFile ) {
397
406
398
407
// `originalRequiredFile != requiredFile` then the file is being referenced through
399
408
// the wrong namespace even though it is being `goog.require`d in the file.
400
409
401
410
DiagnosticType toReport =
402
- isStrongReference
411
+ referenceStrength . isStrong
403
412
? INDIRECT_NAMESPACE_REF_REQUIRE
404
413
: INDIRECT_NAMESPACE_REF_REQUIRE_TYPE ;
405
414
t .report (n , toReport , namespace );
@@ -418,14 +427,14 @@ private void checkMissingRequireThroughShortName(
418
427
*
419
428
* @param n the node representing the fully qualified name being checked
420
429
* @param qualifiedName some global fully qualified name to check
421
- * @param isStrongReference whether this is a non-type-only reference
430
+ * @param referenceStrength whether this is a non-type-only reference
422
431
*/
423
432
private void checkMissingRequireThroughFullyQualifiedName (
424
433
NodeTraversal t ,
425
434
Node n ,
426
435
ModuleMetadata currentFile ,
427
436
QualifiedName qualifiedName ,
428
- boolean isStrongReference ) {
437
+ Strength referenceStrength ) {
429
438
430
439
// Look for the longest prefix match against a provided namespace.
431
440
for (QualifiedName subName = qualifiedName ; subName != null ; subName = subName .getOwner ()) {
@@ -446,16 +455,23 @@ private void checkMissingRequireThroughFullyQualifiedName(
446
455
* In files that represent modules, report a require without an alias the same as a totally
447
456
* missing require.
448
457
*/
449
- toReport = isStrongReference ? MISSING_REQUIRE : MISSING_REQUIRE_TYPE ;
450
- } else if (!hasAcceptableRequire (currentFile , subName , requiredFile , isStrongReference )) {
458
+ toReport = referenceStrength . isStrong ? MISSING_REQUIRE : MISSING_REQUIRE_TYPE ;
459
+ } else if (!hasAcceptableRequire (currentFile , subName , requiredFile , referenceStrength )) {
451
460
/*
452
461
* In files that aren't modules, report a qualified name reference only if there's no
453
462
* require to satisfy it.
454
463
*/
455
464
toReport =
456
- isStrongReference
465
+ referenceStrength . isStrong
457
466
? MISSING_REQUIRE_IN_PROVIDES_FILE
458
467
: MISSING_REQUIRE_TYPE_IN_PROVIDES_FILE ;
468
+ } else if (!referenceStrength .isTypeOnly && requiredFile .isNonLegacyGoogModule ()) {
469
+ // The referenced file has a valid goog.require for it, but the reference cannot be in a
470
+ // regular qualified name: that won't be defined at runtime.
471
+ // However, this only applies to references in actual code. Type-only references may still
472
+ // reference the namespace in a script. The Closure type system will still resolve the
473
+ // namespace - it just doesn't have any runtime effect.
474
+ toReport = NON_LEGACY_GOOG_MODULE_REFERENCE ;
459
475
} else {
460
476
return ;
461
477
}
@@ -515,9 +531,12 @@ boolean isAllowedNamespace(ModuleMetadata currentFile, String namespace) {
515
531
* different files.
516
532
*/
517
533
private static boolean hasAcceptableRequire (
518
- ModuleMetadata rdep , QualifiedName namespace , ModuleMetadata dep , boolean isStrongReference ) {
534
+ ModuleMetadata rdep ,
535
+ QualifiedName namespace ,
536
+ ModuleMetadata dep ,
537
+ Strength referenceStrength ) {
519
538
Set <String > acceptableRequires = rdep .stronglyRequiredGoogNamespaces ().elementSet ();
520
- if (!isStrongReference ) {
539
+ if (!referenceStrength . isStrong ) {
521
540
acceptableRequires =
522
541
Sets .union (acceptableRequires , rdep .weaklyRequiredGoogNamespaces ().elementSet ());
523
542
}
@@ -531,4 +550,23 @@ private static boolean hasAcceptableRequire(
531
550
532
551
return false ;
533
552
}
553
+
554
+ /**
555
+ * Represents the strength of references of an require'd name, i.e. whether references are in code
556
+ * only (strong, not-typeOnly), JsDoc annotations for \@implements or \@extends (strong as well as
557
+ * typeOnly) or in other JsDoc annotations only (weak, typeOnly).
558
+ */
559
+ enum Strength {
560
+ CODE (true , false ),
561
+ IMPLEMENTS_EXTENDS (true , true ),
562
+ WEAK_TYPE (false , true );
563
+
564
+ Strength (boolean isStrong , boolean isTypeOnly ) {
565
+ this .isStrong = isStrong ;
566
+ this .isTypeOnly = isTypeOnly ;
567
+ }
568
+
569
+ final boolean isStrong ;
570
+ final boolean isTypeOnly ;
571
+ }
534
572
}
0 commit comments