@@ -7,7 +7,6 @@ import 'package:analyzer/dart/ast/visitor.dart';
77import 'package:analyzer/dart/element/element.dart' ;
88import 'package:analyzer/dart/element/nullability_suffix.dart' ;
99import 'package:analyzer/dart/element/type.dart' ;
10- import 'package:analyzer/dart/element/type_provider.dart' ;
1110// ignore: implementation_imports
1211import 'package:analyzer/src/dart/element/type.dart' ;
1312// ignore: implementation_imports
@@ -32,74 +31,33 @@ const Set<String> _sdkWebLibraries = {
3231 'dart:web_gl' ,
3332};
3433
35- /// If [type] is a type declared using `@staticInterop` through
36- /// `dart:js_interop` , returns the JS type equivalent for that class, which is
37- /// just `JSObject` .
38- ///
39- /// `@staticInterop` types that were declared using `package:js` do not apply as
40- /// that package is incompatible with dart2wasm.
41- ///
42- /// Returns null if `type` is not a `dart:js_interop` `@staticInterop` class.
43- DartType ? getJsTypeForStaticInterop (InterfaceType type) {
44- var element = type.element;
45- if (element is ! ClassElement ) return null ;
46- var metadata = element.metadata;
47- var hasJS = false ;
48- var hasStaticInterop = false ;
49- late LibraryElement dartJsInterop;
50- for (var i = 0 ; i < metadata.length; i++ ) {
51- var annotation = metadata[i];
52- var annotationElement = annotation.element;
53- if (annotationElement is ConstructorElement &&
54- isFromLibrary (annotationElement.library, _dartJsInteropUri) &&
55- annotationElement.enclosingElement3.name == 'JS' ) {
56- hasJS = true ;
57- dartJsInterop = annotationElement.library;
58- } else if (annotationElement is PropertyAccessorElement &&
59- isFromLibrary (annotationElement.library, _dartJsAnnotationsUri) &&
60- annotationElement.name == 'staticInterop' ) {
61- hasStaticInterop = true ;
62- }
63- }
64- return (hasJS && hasStaticInterop)
65- ? dartJsInterop.units.single.extensionTypes
66- .singleWhere ((extType) => extType.name == 'JSObject' )
67- // Nullability is ignored in this lint, so just return `thisType`.
68- .thisType
69- : null ;
70- }
71-
7234/// Given a [type] erased by [EraseNonJSInteropTypes] , determine if it is a type
7335/// that is a `dart:js_interop` type or is bound to one.
74- bool isDartJsInteropType (DartType type) {
75- if (type is TypeParameterType ) return isDartJsInteropType (type.bound);
36+ bool _isDartJsInteropType (DartType type) {
37+ if (type is TypeParameterType ) return _isDartJsInteropType (type.bound);
7638 if (type is InterfaceType ) {
7739 var element = type.element;
7840 if (element is ExtensionTypeElement ) {
79- return isFromLibrary ( element.library, _dartJsInteropUri);
41+ return element.isFromLibrary ( _dartJsInteropUri);
8042 }
8143 }
8244 return false ;
8345}
8446
85- bool isFromLibrary (LibraryElement elementLibrary, String uri) =>
86- elementLibrary.definingCompilationUnit.source ==
87- elementLibrary.context.sourceFactory.forUri (uri);
88-
8947/// Whether [type] is a user JS interop type using `dart:js_interop` or is bound
9048/// to one.
9149///
9250/// An interop type is a user interop type if it's an extension type on another
9351/// interop type or a `dart:js_interop` `@staticInterop` type.
94- bool isUserJsInteropType (DartType type) {
95- if (type is TypeParameterType ) return isUserJsInteropType (type.bound);
52+ bool _isUserJsInteropType (DartType type) {
53+ if (type is TypeParameterType ) return _isUserJsInteropType (type.bound);
9654 if (type is InterfaceType ) {
9755 var element = type.element;
9856 if (element is ExtensionTypeElement ) {
9957 var representationType = element.representation.type;
100- return isDartJsInteropType (representationType) ||
101- isUserJsInteropType (representationType);
102- } else if (getJsTypeForStaticInterop (type) != null ) {
58+ return _isDartJsInteropType (representationType) ||
59+ _isUserJsInteropType (representationType);
60+ } else if (_jsTypeForStaticInterop (type) != null ) {
10361 return true ;
10462 }
10563 }
@@ -115,17 +73,55 @@ bool isUserJsInteropType(DartType type) {
11573/// Since dart2wasm doesn't support these libraries, users can't come across
11674/// platform-inconsistent type tests, and therefore we should not lint for these
11775/// types.
118- bool isWasmIncompatibleJsInterop (DartType type) {
119- if (type is TypeParameterType ) return isWasmIncompatibleJsInterop (type.bound);
76+ bool _isWasmIncompatibleJsInterop (DartType type) {
77+ if (type is TypeParameterType ) {
78+ return _isWasmIncompatibleJsInterop (type.bound);
79+ }
12080 if (type is ! InterfaceType ) return false ;
12181 var element = type.element;
12282 // `hasJS` only checks for the `dart:_js_annotations` definition, which is
12383 // what we want here.
12484 if (element.hasJS) return true ;
125- return _sdkWebLibraries.any ((uri) => isFromLibrary ( element.library, uri)) ||
85+ return _sdkWebLibraries.any ((uri) => element.isFromLibrary ( uri)) ||
12686 // While a type test with types from this library is very rare, we should
12787 // still ignore it for consistency.
128- isFromLibrary (element.library, _dartJsUri);
88+ element.isFromLibrary (_dartJsUri);
89+ }
90+
91+ /// If [type] is a type declared using `@staticInterop` through
92+ /// `dart:js_interop` , returns the JS type equivalent for that class, which is
93+ /// just `JSObject` .
94+ ///
95+ /// `@staticInterop` types that were declared using `package:js` do not apply as
96+ /// that package is incompatible with dart2wasm.
97+ ///
98+ /// Returns null if `type` is not a `dart:js_interop` `@staticInterop` class.
99+ DartType ? _jsTypeForStaticInterop (InterfaceType type) {
100+ var element = type.element;
101+ if (element is ! ClassElement ) return null ;
102+ var metadata = element.metadata;
103+ var hasJS = false ;
104+ var hasStaticInterop = false ;
105+ late LibraryElement dartJsInterop;
106+ for (var annotation in metadata) {
107+ var annotationElement = annotation.element;
108+ if (annotationElement is ConstructorElement &&
109+ annotationElement.isFromLibrary (_dartJsInteropUri) &&
110+ annotationElement.enclosingElement3.name == 'JS' ) {
111+ hasJS = true ;
112+ dartJsInterop = annotationElement.library;
113+ } else if (annotationElement is PropertyAccessorElement &&
114+ annotationElement.isFromLibrary (_dartJsAnnotationsUri) &&
115+ annotationElement.name == 'staticInterop' ) {
116+ hasStaticInterop = true ;
117+ }
118+ }
119+ return (hasJS && hasStaticInterop)
120+ ? dartJsInterop.units.single.extensionTypes
121+ .singleWhere ((extType) => extType.name == 'JSObject' )
122+ // Nullability is ignored in this lint, so just return `thisType`.
123+ .thisType
124+ : null ;
129125}
130126
131127/// Erases extension types except for JS interop types so that
@@ -138,8 +134,6 @@ class EraseNonJSInteropTypes extends ExtensionTypeErasure {
138134
139135 final _visitedTypes = < DartType > {};
140136
141- EraseNonJSInteropTypes ();
142-
143137 @override
144138 DartType perform (DartType type, {bool keepUserInteropTypes = false }) {
145139 _keepUserInteropTypes = keepUserInteropTypes;
@@ -150,13 +144,13 @@ class EraseNonJSInteropTypes extends ExtensionTypeErasure {
150144 @override
151145 DartType ? visitInterfaceType (covariant InterfaceTypeImpl type) {
152146 if (_keepUserInteropTypes
153- ? isUserJsInteropType (type)
154- : isDartJsInteropType (type)) {
147+ ? _isUserJsInteropType (type)
148+ : _isDartJsInteropType (type)) {
155149 // Nullability and generics on interop types are ignored for this lint. In
156150 // order to just compare the interfaces themselves, we use `thisType`.
157151 return type.element.thisType;
158152 } else {
159- return getJsTypeForStaticInterop (type) ?? super .visitInterfaceType (type);
153+ return _jsTypeForStaticInterop (type) ?? super .visitInterfaceType (type);
160154 }
161155 }
162156
@@ -200,7 +194,7 @@ class InvalidRuntimeCheckWithJSInteropTypes extends LintRule {
200194 @override
201195 void registerNodeProcessors (
202196 NodeLintRegistry registry, LinterContext context) {
203- var visitor = _Visitor (this , context.typeSystem, context.typeProvider );
197+ var visitor = _Visitor (this , context.typeSystem);
204198 registry.addIsExpression (this , visitor);
205199 registry.addAsExpression (this , visitor);
206200 }
@@ -209,10 +203,9 @@ class InvalidRuntimeCheckWithJSInteropTypes extends LintRule {
209203class _Visitor extends SimpleAstVisitor <void > {
210204 final LintRule rule;
211205 final TypeSystemImpl typeSystem;
212- final TypeProvider typeProvider;
213- late final EraseNonJSInteropTypes _eraseNonJsInteropTypes;
206+ final EraseNonJSInteropTypes _eraseNonJsInteropTypes;
214207
215- _Visitor (this .rule, TypeSystem typeSystem, this .typeProvider )
208+ _Visitor (this .rule, TypeSystem typeSystem)
216209 : typeSystem = typeSystem as TypeSystemImpl ,
217210 _eraseNonJsInteropTypes = EraseNonJSInteropTypes ();
218211
@@ -254,73 +247,71 @@ class _Visitor extends SimpleAstVisitor<void> {
254247 /// Types that belong to JS interop libraries that are not available when
255248 /// compiling to Wasm are ignored. Nullability is also ignored for the purpose
256249 /// of this test.
257- LintCode ? getInvalidJsInteropTypeTest (DartType ? leftType, DartType ? rightType,
250+ LintCode ? getInvalidJsInteropTypeTest (DartType leftType, DartType rightType,
258251 {required bool check}) {
259- if (leftType == null || rightType == null ) return null ;
260252 LintCode ? lintCode;
261253 (DartType , DartType ) eraseTypes (DartType left, DartType right) {
262254 var erasedLeft =
263255 typeSystem.promoteToNonNull (_eraseNonJsInteropTypes.perform (left));
264256 var erasedRight =
265257 typeSystem.promoteToNonNull (_eraseNonJsInteropTypes.perform (right));
266- var leftIsInteropType = isDartJsInteropType (erasedLeft);
267- var rightIsInteropType = isDartJsInteropType (erasedRight);
258+ var leftIsInteropType = _isDartJsInteropType (erasedLeft);
259+ var rightIsInteropType = _isDartJsInteropType (erasedRight);
268260 // If there's already an invalid check in this `canBeSubtypeOf` check, we
269261 // are already going to lint, so only continue checking if we haven't
270262 // found an issue.
271- if (lintCode == null ) {
272- if (leftIsInteropType || rightIsInteropType) {
273- if (! isWasmIncompatibleJsInterop (erasedLeft) &&
274- ! isWasmIncompatibleJsInterop (erasedRight)) {
275- var erasedLeftIsSubtype =
276- typeSystem.isSubtypeOf (erasedLeft, erasedRight);
277- var erasedRightIsSubtype =
278- typeSystem.isSubtypeOf (erasedRight, erasedLeft);
279- var erasedLeftIsDynamic = erasedLeft is DynamicType ;
280- var erasedRightIsDynamic = erasedRight is DynamicType ;
281- if (check) {
282- if (! erasedLeftIsSubtype && ! erasedRightIsDynamic) {
283- if (leftIsInteropType && rightIsInteropType) {
284- lintCode = LinterLintCode
285- .invalid_runtime_check_with_js_interop_types_js_is_inconsistent_js;
286- } else if (leftIsInteropType) {
287- lintCode = LinterLintCode
288- .invalid_runtime_check_with_js_interop_types_js_is_dart;
289- } else {
290- lintCode = LinterLintCode
291- .invalid_runtime_check_with_js_interop_types_dart_is_js;
292- }
293- } else if (erasedLeftIsSubtype &&
294- leftIsInteropType &&
295- rightIsInteropType) {
296- // Only report if the right type is a user JS interop type.
297- // Checks like `window is JSAny` are not confusing and not worth
298- // linting.
299- if (isUserJsInteropType (right) &&
300- ! typeSystem.isSubtypeOf (
301- _eraseNonJsInteropTypes.perform (left,
302- keepUserInteropTypes: true ),
303- _eraseNonJsInteropTypes.perform (right,
304- keepUserInteropTypes: true ))) {
305- lintCode = LinterLintCode
306- .invalid_runtime_check_with_js_interop_types_js_is_unrelated_js;
307- }
263+
264+ if (lintCode == null && leftIsInteropType || rightIsInteropType) {
265+ if (! _isWasmIncompatibleJsInterop (erasedLeft) &&
266+ ! _isWasmIncompatibleJsInterop (erasedRight)) {
267+ var erasedLeftIsSubtype =
268+ typeSystem.isSubtypeOf (erasedLeft, erasedRight);
269+ var erasedRightIsSubtype =
270+ typeSystem.isSubtypeOf (erasedRight, erasedLeft);
271+ var erasedLeftIsDynamic = erasedLeft is DynamicType ;
272+ var erasedRightIsDynamic = erasedRight is DynamicType ;
273+ if (check) {
274+ if (! erasedLeftIsSubtype && ! erasedRightIsDynamic) {
275+ if (leftIsInteropType && rightIsInteropType) {
276+ lintCode = LinterLintCode
277+ .invalid_runtime_check_with_js_interop_types_js_is_inconsistent_js;
278+ } else if (leftIsInteropType) {
279+ lintCode = LinterLintCode
280+ .invalid_runtime_check_with_js_interop_types_js_is_dart;
281+ } else {
282+ lintCode = LinterLintCode
283+ .invalid_runtime_check_with_js_interop_types_dart_is_js;
308284 }
309- } else {
310- if (! erasedLeftIsSubtype &&
311- ! erasedRightIsSubtype &&
312- ! erasedLeftIsDynamic &&
313- ! erasedRightIsDynamic) {
314- if (leftIsInteropType && rightIsInteropType) {
315- lintCode = LinterLintCode
316- .invalid_runtime_check_with_js_interop_types_js_as_incompatible_js;
317- } else if (leftIsInteropType) {
318- lintCode = LinterLintCode
319- .invalid_runtime_check_with_js_interop_types_js_as_dart;
320- } else {
321- lintCode = LinterLintCode
322- .invalid_runtime_check_with_js_interop_types_dart_as_js;
323- }
285+ } else if (erasedLeftIsSubtype &&
286+ leftIsInteropType &&
287+ rightIsInteropType) {
288+ // Only report if the right type is a user JS interop type.
289+ // Checks like `window is JSAny` are not confusing and not worth
290+ // linting.
291+ if (_isUserJsInteropType (right) &&
292+ ! typeSystem.isSubtypeOf (
293+ _eraseNonJsInteropTypes.perform (left,
294+ keepUserInteropTypes: true ),
295+ _eraseNonJsInteropTypes.perform (right,
296+ keepUserInteropTypes: true ))) {
297+ lintCode = LinterLintCode
298+ .invalid_runtime_check_with_js_interop_types_js_is_unrelated_js;
299+ }
300+ }
301+ } else {
302+ if (! erasedLeftIsSubtype &&
303+ ! erasedRightIsSubtype &&
304+ ! erasedLeftIsDynamic &&
305+ ! erasedRightIsDynamic) {
306+ if (leftIsInteropType && rightIsInteropType) {
307+ lintCode = LinterLintCode
308+ .invalid_runtime_check_with_js_interop_types_js_as_incompatible_js;
309+ } else if (leftIsInteropType) {
310+ lintCode = LinterLintCode
311+ .invalid_runtime_check_with_js_interop_types_js_as_dart;
312+ } else {
313+ lintCode = LinterLintCode
314+ .invalid_runtime_check_with_js_interop_types_dart_as_js;
324315 }
325316 }
326317 }
@@ -334,6 +325,7 @@ class _Visitor extends SimpleAstVisitor<void> {
334325 return (erasedLeft, erasedRight);
335326 }
336327
328+ // Called here for the side effects of `eraseTypes`.
337329 typeSystem.canBeSubtypeOf (leftType, rightType, eraseTypes: eraseTypes);
338330 return lintCode;
339331 }
@@ -342,29 +334,28 @@ class _Visitor extends SimpleAstVisitor<void> {
342334 void visitAsExpression (AsExpression node) {
343335 var leftType = node.expression.staticType;
344336 var rightType = node.type.type;
337+ if (leftType == null || rightType == null ) return ;
345338 var code = getInvalidJsInteropTypeTest (leftType, rightType, check: false );
346339 if (code != null ) {
347- rule.reportLint (node,
348- arguments: [
349- leftType! .getDisplayString (),
350- rightType! .getDisplayString (),
351- ],
352- errorCode: code);
340+ rule.reportLint (node, arguments: [leftType, rightType], errorCode: code);
353341 }
354342 }
355343
356344 @override
357345 void visitIsExpression (IsExpression node) {
358346 var leftType = node.expression.staticType;
359347 var rightType = node.type.type;
348+ if (leftType == null || rightType == null ) return ;
360349 var code = getInvalidJsInteropTypeTest (leftType, rightType, check: true );
361350 if (code != null ) {
362- rule.reportLint (node,
363- arguments: [
364- leftType! .getDisplayString (),
365- rightType! .getDisplayString (),
366- ],
367- errorCode: code);
351+ rule.reportLint (node, arguments: [leftType, rightType], errorCode: code);
368352 }
369353 }
370354}
355+
356+ extension on Element {
357+ /// Returns whether this is from the Dart library at [uri] .
358+ bool isFromLibrary (String uri) =>
359+ library? .definingCompilationUnit.source ==
360+ context.sourceFactory.forUri (uri);
361+ }
0 commit comments