From 6936e019936a887aee4a629b0a5a35fb5536df7c Mon Sep 17 00:00:00 2001 From: Parker Lougheed Date: Wed, 8 Jan 2025 06:31:16 +0800 Subject: [PATCH 1/3] Update upstream docs for latest lint and diagnostic updates (#6307) --- src/_data/linter_rules.json | 27 +++ src/content/tools/diagnostic-messages.md | 265 ++++++++++++++++------- 2 files changed, 215 insertions(+), 77 deletions(-) diff --git a/src/_data/linter_rules.json b/src/_data/linter_rules.json index 670d382ff6..a6fcce0b1f 100644 --- a/src/_data/linter_rules.json +++ b/src/_data/linter_rules.json @@ -2656,6 +2656,19 @@ "details": "Do type annotate initialized top-level or static variables when the type is\nnon-obvious.\n\nType annotations on top-level or static variables can serve as a request for\ntype inference, documenting the expected outcome of the type inference step,\nand declaratively allowing the compiler and analyzer to solve the possibly\ncomplex task of finding type arguments and annotations in the initializing\nexpression that yield the desired result.\n\nType annotations on top-level or static variables can also inform readers about\nthe type of the initializing expression, which will allow them to proceed\nreading the locations in code where this variable is used with known good\ninformation about the type of the given variable (which may not be immediately\nevident by looking at the initializing expression).\n\nAn expression is considered to have a non-obvious type when it does not\nhave an obvious type.\n\nAn expression e has an obvious type in the following cases:\n\n- e is a non-collection literal. For instance, 1, true, 'Hello, $name!'.\n- e is a collection literal with actual type arguments. For instance,\n {}.\n- e is a list literal or a set literal where at least one element has an\n obvious type, and all elements have the same type. For instance, [1, 2] and\n { [true, false], [] }, but not [1, 1.5].\n- e is a map literal where all key-value pair have a key with an obvious type\n and a value with an obvious type, and all keys have the same type, and all\n values have the same type. For instance, { #a: [] }, but not\n {1: 1, 2: true}.\n- e is an instance creation expression whose class part is not raw. For\n instance C(14) if C is a non-generic class, or C(14) if C accepts one\n type argument, but not C(14) if C accepts one or more type arguments.\n- e is a cascade whose target has an obvious type. For instance,\n 1..isEven..isEven has an obvious type because 1 has an obvious type.\n- e is a type cast. For instance, myComplexpression as int.\n\n**BAD:**\n```dart\nfinal myTopLevelVariable =\n genericFunctionWrittenByOtherFolks(with, args);\n\nclass A {\n static var myStaticVariable =\n myTopLevelVariable.update('foo', null);\n}\n```\n\n**GOOD:**\n```dart\nfinal Map myTopLevelVariable =\n genericFunctionWrittenByOtherFolks(with, args);\n\nclass A {\n static Map myStaticVariable =\n myTopLevelVariable.update('foo', null);\n}\n```\n\n**This rule is experimental.** It is being evaluated, and it may be changed\nor removed. Feedback on its behavior is welcome! The main issue is here:\nhttps://github.com/dart-lang/linter/issues/5101.", "sinceDartSdk": "3.7-wip" }, + { + "name": "strict_top_level_inference", + "description": "Specify type annotations.", + "categories": [ + "style" + ], + "state": "stable", + "incompatible": [], + "sets": [], + "fixStatus": "hasFix", + "details": "Do type annotate top-level and class-like member declarations, where types\nare not inferred from super-interfaces or initializers.\n\nThe lint warns about every omitted return type, parameter type, and\nvariable type of a top-level declaration or class-like-namespace-level\ndeclaration (static or instance member or constructor declaration), which\nis not given a type by inference, and which therefore defaults to dynamic.\n\nThe only omitted types that can be given a type by top-level inference,\nare those of variable declarations with initializer expressions, and\nreturn and parameter types of instance members that override a consistent\ncombined super-interface signature.\n\nSetters do not need a return type, as it is always assumed to be `void`.\n\n**BAD:**\n```dart\nvar _zeroPointCache;\nclass Point {\n get zero => ...;\n final x, y;\n Point(x, y) {}\n closest(b, c) => distance(b) <= distance(c) ? b : c;\n distance(other) => ...;\n}\n_sq(v) => v * v;\n```\n\n**GOOD:**\n```dart\nPoint? _zeroPointCache;\nclass Point {\n Point get zero => ...;\n final int x, y;\n Point(int x, int y) {}\n closest(Point b, Point c) =>\n distance(b) <= distance(c) ? b : c;\n distance(Point other) => ...;\n}\nint _sq(int v) => v * v;\n```", + "sinceDartSdk": "3.7-wip" + }, { "name": "super_goes_last", "description": "Place the `super` call last in a constructor initialization list.", @@ -3179,6 +3192,20 @@ "details": "Unnecessary `toList()` in spreads.\n\n**BAD:**\n```dart\nchildren: [\n ...['foo', 'bar', 'baz'].map((String s) => Text(s)).toList(),\n]\n```\n\n**GOOD:**\n```dart\nchildren: [\n ...['foo', 'bar', 'baz'].map((String s) => Text(s)),\n]\n```", "sinceDartSdk": "2.18" }, + { + "name": "unnecessary_underscores", + "description": "Unnecessary underscores can be removed.", + "categories": [ + "brevity", + "style" + ], + "state": "stable", + "incompatible": [], + "sets": [], + "fixStatus": "needsFix", + "details": "**AVOID** using multiple underscores when a single wildcard will do.\n\n**BAD:**\n```dart\nvoid function(int __) { }\n```\n\n**GOOD:**\n```dart\nvoid function(int _) { }\n```", + "sinceDartSdk": "3.7-wip" + }, { "name": "unreachable_from_main", "description": "Unreachable top-level members in executable libraries.", diff --git a/src/content/tools/diagnostic-messages.md b/src/content/tools/diagnostic-messages.md index f959a26fa6..f86ff232fd 100644 --- a/src/content/tools/diagnostic-messages.md +++ b/src/content/tools/diagnostic-messages.md @@ -14425,6 +14425,118 @@ final class C extends Struct { } ``` +### native_function_missing_type + +_The native type of this function couldn't be inferred so it must be specified +in the annotation._ + +#### Description + +The analyzer produces this diagnostic when a `@Native`-annotated function +requires a type hint on the annotation to infer the native function type. + +Dart types like `int` and `double` have multiple possible native +representations. Since the native type needs to be known at compile time +to generate correct bindings and call instructions for the function, an +explicit type must be given. + +For more information about FFI, see [C interop using dart:ffi][ffi]. + +#### Example + +The following code produces this diagnostic because the function `f()` has +the return type `int`, but doesn't have an explicit type parameter on the +`Native` annotation: + +```dart +import 'dart:ffi'; + +@Native() +external int [!f!](); +``` + +#### Common fixes + +Add the corresponding type to the annotation. For instance, if `f()` was +declared to return an `int32_t` in C, the Dart function should be declared +as: + +```dart +import 'dart:ffi'; + +@Native() +external int f(); +``` + +### negative_variable_dimension + +_The variable dimension of a variable-length array must be non-negative._ + +#### Description + +The analyzer produces this diagnostic in two cases. + +The first is when the variable dimension given in an +`Array.variableWithVariableDimension` annotation is negative. The variable +dimension is the first argument in the annotation. + +The second is when the variable dimension given in an +`Array.variableMulti` annotation is negative. The variable dimension is +specified in the `variableDimension` argument of the annotation. + +For more information about FFI, see [C interop using dart:ffi][ffi]. + +#### Examples + +The following code produces this diagnostic because a variable dimension +of `-1` was provided in the `Array.variableWithVariableDimension` +annotation: + +```dart +import 'dart:ffi'; + +final class MyStruct extends Struct { + @Array.variableWithVariableDimension([!-1!]) + external Array a0; +} +``` + +The following code produces this diagnostic because a variable dimension +of `-1` was provided in the `Array.variableMulti` annotation: + +```dart +import 'dart:ffi'; + +final class MyStruct2 extends Struct { + @Array.variableMulti(variableDimension: [!-1!], [1, 2]) + external Array>> a0; +} +``` + +#### Common fixes + +Change the variable dimension with zero (`0`) or a positive number: + +```dart +import 'dart:ffi'; + +final class MyStruct extends Struct { + @Array.variableWithVariableDimension(1) + external Array a0; +} +``` + +Change the variable dimension with zero (`0`) or a positive number: + +```dart +import 'dart:ffi'; + +final class MyStruct2 extends Struct { + @Array.variableMulti(variableDimension: 1, [1, 2]) + external Array>> a0; +} +``` + ### new_with_undefined_constructor_default _The class '{0}' doesn't have an unnamed constructor._ @@ -15719,7 +15831,7 @@ For more information about FFI, see [C interop using dart:ffi][ffi]. #### Example The following code produces this diagnostic because an array dimension of -`-1` was provided: +`-8` was provided: ```dart import 'dart:ffi'; @@ -15743,7 +15855,8 @@ final class MyStruct extends Struct { } ``` -If this is a variable length inline array, change the annotation to `Array.variable()`: +If this is a variable length inline array, change the annotation to +`Array.variable()`: ```dart import 'dart:ffi'; @@ -29099,6 +29212,32 @@ List toLowercase(List strings) { } ``` +### unnecessary_underscores + +_Unnecessary use of multiple underscores._ + +#### Description + +The analyzer produces this diagnostic when an unused variable is named +with multiple underscores (for example `__`). +A single `_` wildcard variable can be used instead. + +#### Example + +The following code produces this diagnostic because the `__` parameter is unused: + +```dart +void function(int [!__!]) { } +``` + +#### Common fixes + +Replace the name with a single underscore: + +```dart +void function(int _) { } +``` + ### unrelated_type_equality_checks _The type of the operand ('{0}') isn't a subtype or a supertype of the value @@ -29154,111 +29293,83 @@ _This type is unsafe: a type parameter occurs in a non-covariant position._ #### Description -This lint warns against declaring non-covariant members. - -An instance variable whose type contains a type parameter of the -enclosing class, mixin, or enum in a non-covariant position is -likely to cause run-time failures due to failing type -checks. For example, in `class C {...}`, an instance variable -of the form `void Function(X) myVariable;` may cause this kind -of run-time failure. - -The same is true for a getter or method whose return type has a -non-covariant occurrence of a type parameter of the enclosing -declaration. - -This lint flags this kind of member declaration. +The analyzer produces this diagnostic when an instance member has a result +type which is [contravariant or invariant](https://dart.dev/resources/glossary#variance) +in a type parameter of the enclosing declaration. The result type of a +variable is its type, and the result type of a getter or method is its +return type. This lint warns against such members because they are likely +to cause a failing type check at run time, with no static warning or error +at the call site. #### Example -**BAD:** +The following code produces this diagnostic because `X` occurs +as a parameter type in the type of `f`, which is a +contravariant occurrence of this type parameter: + ```dart class C { - final bool Function([!X!]) fun; // LINT - C(this.fun); -} - -void main() { - C c = C((i) => i.isEven); - c.fun(10); // Throws. + bool Function([!X!]) f; + C(this.f); } ``` -The problem is that `X` occurs as a parameter type in the type -of `fun`. +This is unsafe: If `c` has static type `C` and run-time type `C` +then `c.f` will throw. Hence, every invocation `c.f(a)` will also throw, +even in the case where `a` has a correct type as an argument to `c.f`. #### Common fixes -One way to reduce the potential for run-time type errors is to -ensure that the non-covariant member `fun` is _only_ used on -`this`. We cannot strictly enforce this, but we can make it -private and add a forwarding method `fun` such that we can check -locally in the same library that this constraint is satisfied: +If the linted member is or can be private then you may be able +to enforce that it is never accessed on any other receiver than `this`. +This is sufficient to ensure that that the run-time type error does not +occur. For example: -**BETTER:** ```dart class C { + // NB: Ensure manually that `_f` is only accessed on `this`. // ignore: unsafe_variance - final bool Function(X) _fun; - bool fun(X x) => _fun(x); - C(this._fun); -} + bool Function(X) _f; -void main() { - C c = C((i) => i.isEven); - c.fun(10); // Succeeds. + C(this._f); + + // We can write a forwarding method to allow clients to call `_f`. + bool f(X x) => _f(x); } ``` -A fully safe approach requires a feature that Dart does not yet -have, namely statically checked variance. With that, we could -specify that the type parameter `X` is invariant (`inout X`). - -It is possible to emulate invariance without support for statically -checked variance. This puts some restrictions on the creation of -subtypes, but faithfully provides the typing that `inout` would -give: +You can eliminate the unsafe variance by using a more general type for +the linted member. In this case you may need to check the run-time type +and perform a downcast at call sites. -**GOOD:** ```dart -typedef Inv = X Function(X); -typedef C = _C>; - -class _C> { - // ignore: unsafe_variance - final bool Function(X) fun; // Safe! - _C(this.fun); -} - -void main() { - C c = C((i) => i.isEven); - c.fun(10); // Succeeds. +class C { + bool Function(Never) f; + C(this.f); } ``` -With this approach, `C` is not a subtype of `C`, so -`c` must have a different declared type. +If `c` has static type `C` then you may test the type. For example, +`c.f is bool Function(num)`. You may safely call it with an argument of +type `num` if it has that type. -Another possibility is to declare the variable to have a safe -but more general type. It is then safe to use the variable -itself, but every invocation will have to be checked at run -time: +You can also eliminate the unsafe variance by using a much more general +type like `Function`, which is essentially the type `dynamic` for +functions. -**HONEST:** ```dart class C { - final bool Function(Never) fun; - C(this.fun); -} - -void main() { - C c = C((int i) => i.isEven); - var cfun = c.fun; // Local variable, enables promotion. - if (cfun is bool Function(int)) cfun(10); // Succeeds. - if (cfun is bool Function(bool)) cfun(true); // Not called. + Function f; + C(this.f); } ``` +This will make `c.f(a)` dynamically safe: It will throw if and only if the +argument `a` does not have the type required by the function. This is +better than the original version because it will not throw because of a +mismatched static type. It only throws when it _must_ throw for soundness +reasons. + ### use_build_context_synchronously _Don't use 'BuildContext's across async gaps, guarded by an unrelated 'mounted' @@ -29272,7 +29383,7 @@ The analyzer produces this diagnostic when a `BuildContext` is referenced by a `StatefulWidget` after an asynchronous gap without first checking the `mounted` property. -Storing a `BuildContext` for later use can lead to difficult to diagnose +Storing a `BuildContext` for later use can lead to difficult-to-diagnose crashes. Asynchronous gaps implicitly store a `BuildContext`, making them easy to overlook for diagnosis. From 788a6fa7ab3e366e03580161f1a5b2bde8da7adf Mon Sep 17 00:00:00 2001 From: Parker Lougheed Date: Wed, 8 Jan 2025 07:16:00 +0800 Subject: [PATCH 2/3] Clean up and format JS interop usage doc (#6308) - Add semantic breaks - Localize markdown link definitions - Fix a few minor grammar and tense issues - Other minor cleanup and clarifications --- src/content/interop/js-interop/mock.md | 1 + src/content/interop/js-interop/usage.md | 417 +++++++++++++----------- 2 files changed, 227 insertions(+), 191 deletions(-) diff --git a/src/content/interop/js-interop/mock.md b/src/content/interop/js-interop/mock.md index 7b772fee1c..d3dde68707 100644 --- a/src/content/interop/js-interop/mock.md +++ b/src/content/interop/js-interop/mock.md @@ -1,5 +1,6 @@ --- title: How to mock JavaScript interop objects +description: Learn how to mock JS interop objects in Dart for testing. --- In this tutorial, you'll learn how to mock JS objects so that you can test diff --git a/src/content/interop/js-interop/usage.md b/src/content/interop/js-interop/usage.md index b5a0dae105..9cf02b7a15 100644 --- a/src/content/interop/js-interop/usage.md +++ b/src/content/interop/js-interop/usage.md @@ -4,41 +4,47 @@ description: How to declare and use JS interop members. --- JS interop provides the mechanisms to interact with JavaScript APIs from Dart. -It allows you to invoke these APIs and interact with the values that you get -from them using an explicit, idiomatic syntax. - -Typically, you access a JavaScript API by making it available somewhere within -the [global JS scope]. To call and receive JS values from this API, you use -[`external` interop members](#interop-members). In order to construct and -provide types for JS values, you use and declare -[interop types](#interop-types), which also contain interop members. To pass -Dart values like `List`s or `Function` to interop members or convert from JS -values to Dart values, you use [conversion functions] unless the interop member -[contains a primitive type]. +It allows you to invoke these APIs and interact with the values that +you get from them using an explicit, idiomatic syntax. + +Typically, you access a JavaScript API by making it available +somewhere within the [global JS scope][]. +To call and receive JS values from this API, +you use [`external` interop members](#interop-members). +To construct and provide types for JS values, you use and declare +[interop types](#interop-types), which also contain interop members. +To pass Dart values like a `List` or `Function` to interop members or +convert from JS values to Dart values, you use [conversion functions][] unless +the interop member [contains a primitive type][]. + +[global JS scope]: https://developer.mozilla.org/docs/Glossary/Global_scope +[conversion functions]: /interop/js-interop/js-types#conversions +[contains a primitive type]: /interop/js-interop/js-types#requirements-on-external-declarations-and-function-tojs ## Interop types -When interacting with a JS value, you need to provide a Dart type for it. You -can do this by either using or declaring an interop type. Interop types are -either a ["JS type"] provided by Dart or an [extension type] wrapping an interop -type. +When interacting with a JS value, you need to provide a Dart type for it. +You can do this by either using or declaring an interop type. +Interop types are either a ["JS type"][] provided by Dart or +an [extension type][] wrapping an interop type. -Interop types allow you to provide an interface for a JS value and lets you -declare interop APIs for its members. They are also used in the signature of -other interop APIs. +Interop types allow you to provide an interface for a JS value and +let you declare interop APIs for its members. +They're also used in the signature of other interop APIs. ```dart extension type Window(JSObject _) implements JSObject {} ``` -`Window` is an interop type for an arbitrary `JSObject`. There is no [runtime -guarantee][] that `Window` is actually a JS [`Window`]. There also is no conflict -with any other interop interface that is defined for the same value. If you want -to check that `Window` is actually a JS `Window`, you can -[check the type of the JS value through interop]. +`Window` is an interop type for an arbitrary `JSObject`. +There's no [runtime guarantee][] that `Window` is actually a JS [`Window`][]. +There also is no conflict with any other interop interface that +is defined for the same value. +If you want to check that `Window` is actually a JS `Window`, +you can [check the type of the JS value through interop][]. -You can also declare your own interop type for the JS types Dart provides by -wrapping them: +You can also declare your own interop type for the JS types that +Dart provides by wrapping them: ```dart extension type Array._(JSArray _) implements JSArray { @@ -46,23 +52,37 @@ extension type Array._(JSArray _) implements JSArray { } ``` -In most cases, you will likely declare an interop type using `JSObject` as the -[representation type] because you're likely interacting with JS objects which -don't have an interop type provided by Dart. +In most cases, you'll likely declare an interop type using +`JSObject` as the [representation type][], because you're likely +interacting with JS objects which don't have an interop type provided by Dart. -Interop types should also generally [implement] their representation type so -that they can be used where the representation type is expected, like in many -APIs in [`package:web`]. +Interop types should also generally [implement][] their representation type so +that they can be used where the representation type is expected, +like in many APIs provided by [`package:web`][]. + +["JS type"]: /interop/js-interop/js-types +[extension type]: /language/extension-types +[`Window`]: https://developer.mozilla.org/docs/Web/API/Window +[runtime guarantee]: /language/extension-types#type-considerations +[check the type of the JS value through interop]: /interop/js-interop/js-types#compatibility-type-checks-and-casts +[representation type]: /language/extension-types#declaration +[implement]: /language/extension-types#implements +[`package:web`]: {{site.pub-pkg}}/web ## Interop members -[`external`] interop members provide an idiomatic syntax for JS members. They -allow you to write a Dart type signature for its arguments and return value. The -types that can be written in the signature of these members have [restrictions]. +[`external`][] interop members provide an idiomatic syntax for JS members. +They allow you to write a Dart type signature for its +arguments and return value. +The types that can be written in the signature of +these members have [restrictions][]. The JS API the interop member corresponds to is determined by a combination of where it's declared, its name, what kind of Dart member it is, and any [renames](#js). +[`external`]: /language/functions#external +[restrictions]: /interop/js-interop/js-types#requirements-on-external-declarations-and-function-tojs + ### Top-level interop members Given the following JS members: @@ -87,16 +107,18 @@ external set name(String value); external bool isNameEmpty(); ``` -Here, there exists a property `name` and a function `isNameEmpty` that are -exposed in the global scope. To access them, you use top-level interop members. -To get and set `name`, you declare and use an interop getter and setter with the -same name. To use `isNameEmpty`, you declare and call an interop function with -the same name. You can declare top-level interop getters, setters, methods, and -fields. Interop fields are equivalent to getter and setter pairs. +Here, there exists a property `name` and a function `isNameEmpty` that +are exposed in the global scope. +To access them, you use top-level interop members. +To get and set `name`, declare and use +an interop getter and setter with the same name. +To use `isNameEmpty`, declare and call an interop function with the same name. +You can declare top-level interop getters, setters, methods, and fields. +Interop fields are equivalent to getter and setter pairs. Top-level interop members must be declared with a [`@JS()`](#js) annotation to -distinguish them from other `external` top-level members, like those that can be -written using `dart:ffi`. +distinguish them from other `external` top-level members, +like those that can be written using `dart:ffi`. ### Interop type members @@ -162,17 +184,19 @@ Within an interop type, you can declare several different types of - **Constructors**. When called, constructors with only positional parameters create a new JS object whose constructor is defined by the name of the - extension type using `new`. For example, calling `Time(0, 0)` in Dart will - generate a JS invocation that looks like `new Time(0, 0)`. Similarly, calling - `Time.onlyHours(0)` will generate a JS invocation that looks like - `new Time(0)`. Note that the JS invocations of the two constructors follow the - same semantics, regardless of whether they're given a Dart name or if they are - a factory. - - - **Object literal constructors**. It is useful sometimes to create a JS - [object literal] that simply contains a number of properties and their - values. In order to do this, you declare a constructor with only named - parameters, where the names of the parameters will be the property names: + extension type using `new`. + For example, calling `Time(0, 0)` in Dart generates a JS invocation that + looks like `new Time(0, 0)`. Similarly, calling + `Time.onlyHours(0)` generates a JS invocation that looks like `new Time(0)`. + Note that the JS invocations of the two constructors follow the + same semantics, regardless of whether they're + given a Dart name or if they're a factory. + + - **Object literal constructors**. It's sometimes useful to + create a JS [object literal][] that simply contains a + number of properties and their values. + In order to do this, declare a constructor with only named + parameters, where the names of the parameters match the property names: ```dart extension type Options._(JSObject o) implements JSObject { @@ -182,10 +206,12 @@ Within an interop type, you can declare several different types of } ``` - A call to `Options(a: 0, b: 1)` will result in creating the JS object - `{a: 0, b: 1}`. The object is defined by the invocation arguments, so - calling `Options(a: 0)` would result in `{a: 0}`. You can get or set the - properties of the object through `external` instance members. + A call to `Options(a: 0, b: 1)` results in + creating the JS object `{a: 0, b: 1}`. + The object is defined by the invocation arguments, so + calling `Options(a: 0)` results in `{a: 0}`. + You can get or set the properties of the object through + `external` instance members. :::warning Before Dart 3.3.1, object literal constructors required a @@ -193,15 +219,16 @@ Within an interop type, you can declare several different types of To learn more, check out [`dart-lang/sdk#54801`][54801]. ::: -- **`static` members**. Like constructors, these members use the name of the - extension type to generate the JS code. For example, calling - `Time.getTimeDifference(t1, t2)` will generate a JS invocation that looks like - `Time.getTimeDifference(t1, t2)`. Similarly, calling `Time.dinnerTime` will - result in a JS invocation that looks like `Time.dinnerTime`. Like top-levels, +- **`static` members**. Like constructors, static members use + the name of the extension type to generate the JS code. For example, + calling `Time.getTimeDifference(t1, t2)` generates a JS invocation that + looks like `Time.getTimeDifference(t1, t2)`. + Similarly, calling `Time.dinnerTime` results in a JS invocation that + looks like `Time.dinnerTime`. Like top-levels, you can declare `static` methods, getters, setters, and fields. -- **Instance members**. Like with other Dart types, these members require an - instance in order to be used. These members get, set, or invoke properties on +- **Instance members**. Like with other Dart types, instance members require + an instance to be used. These members get, set, or invoke properties on the instance. For example: ```dart @@ -213,16 +240,20 @@ Within an interop type, you can declare several different types of print(time.isDinnerTime()); // true ``` - The call to `dinnerTime.hours` gets the value of the `hours` property of - `dinnerTime`. Similarly, the call to `time.minutes=` sets the value of the - `minutes` property of time. The call to `time.isDinnerTime()` calls the - function in the `isDinnerTime` property of `time` and returns the value. - Like top-levels and `static` members, you can declare instance methods, - getters, setters, and fields. - -- **Operators**. There are only two `external` interop operators allowed in - interop types: `[]` and `[]=`. These are instance members that match the - semantics of JS' [property accessors]. For example, you can declare them like: + The call to `dinnerTime.hours` gets the value of + the `hours` property of `dinnerTime`. + Similarly, the call to `time.minutes=` sets the value of + the `minutes` property of time. + The call to `time.isDinnerTime()` calls the function in + the `isDinnerTime` property of `time` and returns the value. + Like top-levels and `static` members, you can declare + instance methods, getters, setters, and fields. + +- **Operators**. There are only two `external` interop operators + allowed in interop types: `[]` and `[]=`. + These are instance members that + match the semantics of JS' [property accessors][]. + For example, you can declare them like: ```dart extension type Array(JSArray _) implements JSArray { @@ -232,16 +263,23 @@ Within an interop type, you can declare several different types of ``` Calling `array[i]` gets the value in the `i`th slot of `array`, and - `array[i] = i.toJS` sets the value in that slot to `i.toJS`. Other JS - operators are exposed through [utility functions] in `dart:js_interop`. + `array[i] = i.toJS` sets the value in that slot to `i.toJS`. + Other JS operators are exposed by [utility functions][] in `dart:js_interop`. Lastly, like any other extension type, you're allowed to declare any -[non-`external` members] in the interop type. `isMidnight` is one such example. +[non-`external` members][] in the interop type. +A boolean getter `isMidnight` that uses the interop values is one such example. + +[object literal]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Object_initializer +[54801]: {{site.repo.dart.sdk}}/issues/54801 +[property accessors]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Property_accessors#bracket_notation +[utility functions]: {{site.dart-api}}/dart-js_interop/JSAnyOperatorExtension.html +[non-`external` members]: /language/extension-types#members ### Extension members on interop types -You can also write `external` members in [extensions] of interop types. For -example: +You can also write `external` members in [extensions][] of interop types. +For example: ```dart extension on Array { @@ -249,44 +287,54 @@ extension on Array { } ``` -The semantics of calling `push` are identical to what it would have been if it -was in the definition of `Array` instead. Extensions can have `external` -instance members and operators, but cannot have `external` `static` members or -constructors. Like with interop types, you can write any non-`external` members -in the extension. These extensions are useful for when an interop type doesn't -expose the `external` member you need and you don't want to create a new interop -type. +The semantics of calling `push` are identical to what it would have been if +it was in the definition of `Array` instead. +Extensions can have `external` instance members and operators, but +can't have `external` `static` members or constructors. +Like with interop types, you can +write any non-`external` members in the extension. +These extensions are useful for when an interop type doesn't +expose the `external` member you need, and you don't want to +create a new interop type. + +[extensions]: /language/extension-methods ### Parameters `external` interop methods can only contain positional and optional arguments. -This is because JS members only take positional arguments. The one exception is -object literal constructors, where they can contain only named arguments. +This is because JS members only take positional arguments. +The one exception is object literal constructors, +where they can contain only named arguments. -Unlike with non-`external` methods, optional arguments do not get replaced with -their default value, but are instead omitted. For example: +Unlike with non-`external` methods, optional arguments don't get +replaced with their default value, but are instead omitted. +For example: ```dart external int push(JSAny? any, [JSAny? any2]); ``` -Calling `array.push(0.toJS)` in Dart will result in a JS invocation of -`array.push(0.toJS)` and *not* `array.push(0.toJS, null)`. This allows users to -not have to write multiple interop members for the same JS API to avoid passing -in `null`s. If you declare a parameter with an explicit default value, you will +Calling `array.push(0.toJS)` in Dart results in +a JS invocation of `array.push(0.toJS)` and *not* `array.push(0.toJS, null)`. +This allows users to not have to write multiple interop members for +the same JS API to avoid passing in `null`s. +If you declare a parameter with an explicit default value, you get a warning that the value will be ignored. ## `@JS()` -It is sometimes useful to refer to a JS property with a different name than the -one written. For example, if you want to write two `external` APIs that point to -the same JS property, you’d need to write a different name for at least one of -them. Similarly, if you want to define multiple interop types that refer to the -same JS interface, you need to rename at least one of them. Another example is -if the JS name cannot be written in Dart e.g. `$a`. +It's sometimes useful to refer to a JS property with +a different name than the one written. +For example, if you want to write two `external` APIs that +point to the same JS property, you'd need to +write a different name for at least one of them. +Similarly, if you want to define multiple interop types that +refer to the same JS interface, you need to rename at least one of them. +Another example is if the JS name can't be written in Dart, such as `$a`. -In order to do this, you can use the [`@JS()`] annotation with a constant -string value. For example: +To do this, you can use the [`@JS()`][] annotation with +a constant string value. +For example: ```dart extension type Array._(JSArray _) implements JSArray { @@ -296,7 +344,7 @@ extension type Array._(JSArray _) implements JSArray { } ``` -Calling either `push` or `pushString` will result in JS code that uses `push`. +Calling either `push` or `pushString` results in JS code that uses `push`. You can also rename interop types: @@ -309,13 +357,14 @@ extension type JSDate._(JSObject _) implements JSObject { } ``` -Calling `JSDate()` will result in a JS invocation of `new Date()`. Similarly, -calling `JSDate.now()` will result in a JS invocation of `Date.now()`. +Calling `JSDate()` results in a JS invocation of `new Date()`. +Similarly, calling `JSDate.now()` results in a JS invocation of `Date.now()`. -Furthermore, you can namespace an entire library, which will add a prefix to all -interop top-level members, interop types, and `static` interop members within -those types. This is useful if you want to avoid adding too many members to the -global JS scope. +Furthermore, you can namespace an entire library, +adding a prefix to all interop top-level members, interop types, +and `static` interop members within those types. +This is useful if you want to avoid adding +too many members to the global JS scope. ```dart @JS('library1') @@ -333,14 +382,15 @@ extension type JSType._(JSObject _) implements JSObject { } ``` -Calling `method()` will result in a JS invocation of `library1.method()`, -calling `JSType()` will result in a JS invocation of `new library1.JSType()`, -and calling `JSType.staticMember` will result in a JS invocation of -`library1.JSType.staticMember`. +Calling `method()` results in a JS invocation of `library1.method()`, +calling `JSType()` results in a JS invocation of `new library1.JSType()`, +and calling `JSType.staticMember` results in +a JS invocation of `library1.JSType.staticMember`. Unlike interop members and interop types, Dart only ever adds a library name in -the JS invocation if you provide a non-empty value in the `@JS()` annotation on -the library. It does not use the Dart name of the library as the default. +the JS invocation if you provide a non-empty value in +the `@JS()` annotation on the library. +It doesn't use the Dart name of the library as the default. ```dart library interop_library; @@ -351,11 +401,11 @@ import 'dart:js_interop'; external void method(); ``` -Calling `method()` will result in a JS invocation of `method()` and not +Calling `method()` results in a JS invocation of `method()` and not `interop_library.method()`. -You can also write multiple namespaces delimited by a `.` for libraries, -top-level members, and interop types: +You can also write multiple namespaces delimited by a `.` for +libraries, top-level members, and interop types: ```dart @JS('library1.library2') @@ -372,34 +422,39 @@ extension type JSType._(JSObject _) implements JSObject { } ``` -Calling `method()` will result in a JS invocation of -`library1.library2.library3.method()`, calling `JSType()` will result in a JS -invocation of `new library1.library2.library3.JSType()`, and so forth. +Calling `method()` results in +a JS invocation of `library1.library2.library3.method()`, +calling `JSType()` results in +a JS invocation of `new library1.library2.library3.JSType()`, and so forth. -You can't use `@JS()` annotations with `.` in the value on interop type members -or extension members of interop types, however. +However, you can't use `@JS()` annotations with `.` in +the value on interop type members or extension members of interop types. -If there is no value provided to `@JS()` or the value is empty, no renaming will -occur. +If there's no value provided to `@JS()` or the value is empty, +no renaming occurs. -`@JS()` also tells the compiler that a member or type is intended to be treated -as a JS interop member or type. It is required (with or without a value) for all -top-level members to distinguish them from other `external` top-level members, -but can often be elided on and within interop types and on extension members as -the compiler can tell it is a JS interop type from the representation type and -on-type. +`@JS()` also tells the compiler that a member or type is +intended to be treated as a JS interop member or type. +It is required (with or without a value) for all top-level members to +distinguish them from other `external` top-level members, but +can often be elided on and within interop types and on extension members as +the compiler can tell it is a JS interop type from +the representation type and on-type. + +[`@JS()`]: {{site.dart-api}}/dart-js_interop/JS-class.html ## Export Dart functions and objects to JS {:#export} -The above sections show how to call JS members from Dart. It's also useful to -*export* Dart code so that it can be used in JS. To export a Dart function to -JS, first convert it using [`Function.toJS`], which wraps the Dart function with -a JS function. Then, pass the wrapped function to JS through an interop member. +The preceding sections show how to call JS members from Dart. +It's also useful to *export* Dart code so that it can be used in JS. +To export a Dart function to JS, first convert it using [`Function.toJS`][], +which wraps the Dart function with a JS function. +Then, pass the wrapped function to JS through an interop member. At that point, it's ready to be called by other JS code. -For example, this code converts a Dart function and uses interop to set it in a -global property, which is then called in JS: +For example, this code converts a Dart function and uses interop to +set it in a global property, which is then called in JS: ```dart import 'dart:js_interop'; @@ -420,73 +475,59 @@ void main() { globalThis.exportedFunction('hello world'); ``` -Functions that are exported this way have type [restrictions] similar to those -of interop members. +Functions that are exported this way have type [restrictions][] similar to +those of interop members. + +Sometimes it's useful to export an entire Dart interface so that +JS can interact with a Dart object. +To do this, mark the Dart class as exportable using [`@JSExport`][] and +wrap instances of that class using [`createJSInteropWrapper`][]. +For a more detailed explanation of this technique, including how to +mock JS values, check out [How to mock JavaScript interop objects][]. -Sometimes it's useful to export an entire Dart interface so that JS can interact -with a Dart object. To do this, mark the Dart class as exportable using -[`@JSExport`] and wrap instances of that class using [`createJSInteropWrapper`]. -For a more detailed explanation of this technique, including how to mock JS -values, see the [mocking tutorial]. +[`Function.toJS`]: {{site.dart-api}}/dart-js_interop/FunctionToJSExportedDartFunction/toJS.html +[restrictions]: /interop/js-interop/js-types#requirements-on-external-declarations-and-function-tojs +[`@JSExport`]: {{site.dart-api}}/dart-js_interop/JSExport-class.html +[`createJSInteropWrapper`]: {{site.dart-api}}/dart-js_interop/createJSInteropWrapper.html +[How to mock JavaScript interop objects]: /interop/js-interop/mock ## `dart:js_interop` and `dart:js_interop_unsafe` -[`dart:js_interop`] contains all the necessary members you should need, +[`dart:js_interop`][] contains all the necessary members you should need, including `@JS`, JS types, conversion functions, and various utility functions. Utility functions include: -- [`globalContext`], which represents the global scope that the compilers use to - find interop members and types. -- [Helpers to inspect the type of JS values] +- [`globalContext`][], which represents the global scope that + the compilers use to find interop members and types. +- [Helpers to inspect the type of JS values][] - JS operators -- [`dartify`] and [`jsify`], which check the type of certain JS values and - convert them to Dart values and vice versa. Prefer using the specific - conversion when you know the type of the JS value, as the extra type-checking - may be expensive. -- [`importModule`], which allows you to import modules dynamically as - `JSObject`s. +- [`dartify`][] and [`jsify`][], which check the type of certain JS values and + convert them to Dart values and vice versa. + Prefer using the specific conversion when you know the type of the JS value, + as the extra type-checking might be expensive. +- [`importModule`][], which allows you to + import modules dynamically as a `JSObject`. -More utilities may be added to this library in the future. +More utilities might be added to this library in the future. -[`dart:js_interop_unsafe`] contains members that allow you to look up properties -dynamically. For example: +[`dart:js_interop_unsafe`][] contains members that allow +you to look up properties dynamically. For example: ```dart JSFunction f = console['log']; ``` -Instead of declaring an interop member named `log`, we're instead using a string -to represent the property. `dart:js_interop_unsafe` provides functionality to -dynamically get, set, and call properties. +Instead of declaring an interop member named `log`, +a string can be used to access the property. +`dart:js_interop_unsafe` also provides functions to +dynamically check for, get, set, and call properties. :::tip -Avoid using `dart:js_interop_unsafe` if possible. It makes security compliance -more difficult to guarantee and may lead to violations, which is why it can be -"unsafe". +Avoid using `dart:js_interop_unsafe` if possible. +It makes security compliance more difficult to guarantee and +might lead to violations, which is why it can be "unsafe". ::: -{% comment %} -TODO: Some of these are not available on stable. How do we link to dev? -{% endcomment %} - -[global JS scope]: https://developer.mozilla.org/docs/Glossary/Global_scope -[conversion functions]: /interop/js-interop/js-types#conversions -[contains a primitive type]: /interop/js-interop/js-types#requirements-on-external-declarations-and-function-tojs -["JS type"]: /interop/js-interop/js-types -[`Window`]: https://developer.mozilla.org/docs/Web/API/Window -[check the type of the JS value through interop]: /interop/js-interop/js-types#compatibility-type-checks-and-casts -[`package:web`]: {{site.pub-pkg}}/web -[`external`]: /language/functions#external -[restrictions]: /interop/js-interop/js-types#requirements-on-external-declarations-and-function-tojs -[object literal]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Object_initializer -[54801]: {{site.repo.dart.sdk}}/issues/54801 -[property accessors]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Property_accessors#bracket_notation -[utility functions]: {{site.dart-api}}/dart-js_interop/JSAnyOperatorExtension.html -[`@JS()`]: {{site.dart-api}}/dart-js_interop/JS-class.html -[`Function.toJS`]: {{site.dart-api}}/dart-js_interop/FunctionToJSExportedDartFunction/toJS.html -[`@JSExport`]: {{site.dart-api}}/dart-js_interop/JSExport-class.html -[`createJSInteropWrapper`]: {{site.dart-api}}/dart-js_interop/createJSInteropWrapper.html -[mocking tutorial]: /interop/js-interop/mock [`dart:js_interop`]: {{site.dart-api}}/dart-js_interop/dart-js_interop-library.html [`globalContext`]: {{site.dart-api}}/dart-js_interop/globalContext.html [Helpers to inspect the type of JS values]: {{site.dart-api}}/dart-js_interop/JSAnyUtilityExtension.html @@ -494,9 +535,3 @@ TODO: Some of these are not available on stable. How do we link to dev? [`jsify`]: {{site.dart-api}}/dart-js_interop/NullableObjectUtilExtension/jsify.html [`importModule`]: {{site.dart-api}}/dart-js_interop/importModule.html [`dart:js_interop_unsafe`]: {{site.dart-api}}/dart-js_interop_unsafe/dart-js_interop_unsafe-library.html -[extensions]: /language/extension-methods -[extension type]: /language/extension-types -[runtime guarantee]: /language/extension-types#type-considerations -[representation type]: /language/extension-types#declaration -[implement]: /language/extension-types#implements -[non-`external` members]: /language/extension-types#members From 2aaf2229d1c232c3ec4b9856e11563af36ecce31 Mon Sep 17 00:00:00 2001 From: Parker Lougheed Date: Wed, 8 Jan 2025 07:16:15 +0800 Subject: [PATCH 3/3] Explicitly call out legacy JS interop as deprecated (#6310) --- src/content/interop/js-interop/package-web.md | 6 ++-- .../interop/js-interop/past-js-interop.md | 31 +++++++++---------- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/content/interop/js-interop/package-web.md b/src/content/interop/js-interop/package-web.md index 0af00bd3bd..f6c196a931 100644 --- a/src/content/interop/js-interop/package-web.md +++ b/src/content/interop/js-interop/package-web.md @@ -37,12 +37,12 @@ by addressing several concerns with the existing Dart web libraries: if they use [`dart:js_interop`][] and [`dart:js_interop_unsafe`][]. `package:web` is based on `dart:js_interop`, so by default, it's supported on `dart2wasm`. - + Dart core web libraries, like [`dart:html`][html] and [`dart:svg`][svg], - are **not supported** when compiling to Wasm. + are deprecated and **not supported** when compiling to Wasm. 2. **Staying modern** - + `package:web` uses the [Web IDL][idl] to automatically generate [interop members][] and [interop types][] for each declaration in the IDL. diff --git a/src/content/interop/js-interop/past-js-interop.md b/src/content/interop/js-interop/past-js-interop.md index 541c2221d9..43db9523a3 100644 --- a/src/content/interop/js-interop/past-js-interop.md +++ b/src/content/interop/js-interop/past-js-interop.md @@ -1,17 +1,22 @@ --- title: Past JS interop -description: Archive of past JS interop implementations. +description: Archive of Dart's previous JS interop support. --- :::warning None of these legacy interop libraries are supported when compiling to [Wasm][]. ::: -This page addresses previous iterations of JS interop for Dart that are -considered legacy. They are not deprecated yet, but will likely be in the -future. Therefore, prefer using [`dart:js_interop`] going forwards and migrate -usages of old interop libraries when possible. While [`dart:html`] and other web -libraries are closely related, they're covered in the [`package:web`] page. +This page addresses previous iterations of JS interop for Dart that +have been considered legacy and are deprecated as of Dart 3.7. +Therefore, prefer using [`dart:js_interop`][] going forwards and +migrate usages of old interop libraries when possible. +While [`dart:html`][] and other web libraries are closely related, +they're covered in the [`package:web`][] page. + +[`dart:js_interop`]: {{site.dart-api}}/dart-js_interop/dart-js_interop-library.html +[`dart:html`]: {{site.dart-api}}/dart-html/dart-html-library.html +[`package:web`]: /interop/js-interop/package-web ## `dart:js` @@ -23,8 +28,8 @@ code-completion as you couldn't declare interop members and instead relied on Strings. Many of the functionalities exposed in `dart:js` like [`allowInterop`] were later re-exposed through other interop libraries. -This library has been legacy ever since `package:js` and `dart:js_util` were -released. It will likely be the first to be deprecated. +This library has been legacy since +`package:js` and `dart:js_util` were released. ## `package:js` @@ -50,7 +55,7 @@ There are significant differences, however: `package:js` type to `dynamic` and called an interop member on it, it would forward to the right member. This is no longer possible with `dart:js_interop`. -- `package:js`' [`@JS`] has no soundness guarantees as return types of +- `package:js`' `@JS` has no soundness guarantees as return types of `external` members were not checked. `dart:js_interop` is sound. - `package:js` types could not rename instance members or have non-`external` members. @@ -101,20 +106,12 @@ and forth. This included members like: `dart:js_interop` and `dart:js_interop_unsafe` contain these helpers now with possibly alternate syntax. -{% comment %} -TODO: add links (with stable) when ready: -TODO: Link to `package:web` section -{% endcomment %} -[`dart:js_interop`]: {{site.dart-api}}/dart-js_interop/dart-js_interop-library.html -[`dart:html`]: {{site.dart-api}}/dart-html/dart-html-library.html -[`package:web`]: /interop/js-interop/package-web [`dart:js`]: {{site.dart-api}}/dart-js/dart-js-library.html [`object wrapper`]: {{site.dart-api}}/dart-js/JsObject-class.html [`allowInterop`]: {{site.dart-api}}/dart-js_util/allowInterop.html [`package:js`]: {{site.pub-pkg}}/js [`JSObject`]: {{site.dart-api}}/dart-js_interop/JSObject-extension-type.html -[`@JS`]: {{site.repo.dart.sdk}}/blob/main/sdk/lib/js/_js_annotations.dart#L11 [tutorial on mocking]: /interop/js-interop/mock [`@anonymous`]: {{site.repo.dart.sdk}}/blob/main/sdk/lib/js/_js_annotations.dart#L40 [`@staticInterop`]: {{site.repo.dart.sdk}}/blob/main/sdk/lib/js/_js_annotations.dart#L48