Skip to content

Commit edecf19

Browse files
srawlinsCommit Queue
authored andcommitted
Small changes to invalid_runtime_checks_with_js_interop_types
* Make top-level functions private. * Make `isFromLibrary` an extension method, which makes for some nice readability. * Remove the `typeProvider` field from the `_Visitor` class. * Null-check some values before passing to `getInvalidJsInteropTypeTest` which reduces the number of following null-asserts. * Don't convert DartTypes into strings when passing to `reportLint` as there is logic in there to qualify type names with library paths if they have the same name. Change-Id: Ifc0eab77fef774bebd22779e336457223c31ae7c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/389420 Reviewed-by: Srujan Gaddam <[email protected]> Auto-Submit: Samuel Rawlins <[email protected]> Commit-Queue: Srujan Gaddam <[email protected]>
1 parent 81ba893 commit edecf19

File tree

1 file changed

+122
-131
lines changed

1 file changed

+122
-131
lines changed

pkg/linter/lib/src/rules/invalid_runtime_check_with_js_interop_types.dart

Lines changed: 122 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import 'package:analyzer/dart/ast/visitor.dart';
77
import 'package:analyzer/dart/element/element.dart';
88
import 'package:analyzer/dart/element/nullability_suffix.dart';
99
import 'package:analyzer/dart/element/type.dart';
10-
import 'package:analyzer/dart/element/type_provider.dart';
1110
// ignore: implementation_imports
1211
import '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 {
209203
class _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

Comments
 (0)