Skip to content

Commit

Permalink
Add [] and []= operators for unnamed getters and setters (dart-lang#293)
Browse files Browse the repository at this point in the history
Closes dart-lang#127

The IDL defines unnamed getters or setters which should be
emitted as special variants of the index operators.
  • Loading branch information
srujzs authored Aug 28, 2024
1 parent 49f3e6d commit 3619fd8
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 16 deletions.
2 changes: 2 additions & 0 deletions web/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
`dart:io` and `dart:html`.
- Added `JSImmutableListWrapper` which helps create a dart list from a JS list.
- Deprecated `TouchListWrapper` and `TouchListConvert` in favor of `JSImmutableListWrapper`.
- Added `[]` and `[]=` overloaded operators to types which define unnamed
`getter`s and `setter`s, respectively.

## 1.0.0

Expand Down
2 changes: 2 additions & 0 deletions web/lib/src/dom/css_animations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ extension type CSSKeyframeRule._(JSObject _) implements CSSRule, JSObject {
/// API documentation sourced from
/// [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/CSSKeyframesRule).
extension type CSSKeyframesRule._(JSObject _) implements CSSRule, JSObject {
external CSSKeyframeRule operator [](int index);

/// The **`appendRule()`** method of the [CSSKeyframeRule] interface appends a
/// [CSSKeyFrameRule] to the end of the rules.
external void appendRule(String rule);
Expand Down
14 changes: 14 additions & 0 deletions web/lib/src/dom/css_typed_om.dart
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ extension type CSSUnparsedValue._(JSObject _)
implements CSSStyleValue, JSObject {
external factory CSSUnparsedValue(JSArray<CSSUnparsedSegment> members);

external CSSUnparsedSegment operator [](int index);
external void operator []=(
int index,
CSSUnparsedSegment val,
);

/// The **`length`** read-only property of the
/// [CSSUnparsedValue] interface returns the number of items in the object.
external int get length;
Expand Down Expand Up @@ -468,6 +474,8 @@ extension type CSSMathClamp._(JSObject _) implements CSSMathValue, JSObject {
/// API documentation sourced from
/// [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/CSSNumericArray).
extension type CSSNumericArray._(JSObject _) implements JSObject {
external CSSNumericValue operator [](int index);

/// The read-only **`length`** property of the
/// [CSSNumericArray] interface returns the number of
/// [CSSNumericValue] objects in the list.
Expand All @@ -486,6 +494,12 @@ extension type CSSTransformValue._(JSObject _)
implements CSSStyleValue, JSObject {
external factory CSSTransformValue(JSArray<CSSTransformComponent> transforms);

external CSSTransformComponent operator [](int index);
external void operator []=(
int index,
CSSTransformComponent val,
);

/// The **`toMatrix()`** method of the
/// [CSSTransformValue] interface returns a [DOMMatrix] object.
external DOMMatrix toMatrix();
Expand Down
1 change: 1 addition & 0 deletions web/lib/src/dom/dom.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1568,6 +1568,7 @@ extension type Document._(JSObject _) implements Node, JSObject {
/// fullscreen mode, restoring the previous state of the screen. This usually
/// reverses the effects of a previous call to [Element.requestFullscreen].
external JSPromise<JSAny?> exitFullscreen();
external JSObject operator [](String name);

/// The **`getElementsByName()`** method
/// of the [Document] object returns a [NodeList] Collection of
Expand Down
31 changes: 30 additions & 1 deletion web/lib/src/dom/html.dart
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ typedef WorkerType = String;
/// API documentation sourced from
/// [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/HTMLAllCollection).
extension type HTMLAllCollection._(JSObject _) implements JSObject {
external Element operator [](int index);

/// The **`namedItem()`** method of the [HTMLAllCollection] interface returns
/// the first [Element] in the collection whose `id` or `name` attribute
/// matches the specified name, or `null` if no element matches.
Expand Down Expand Up @@ -197,6 +199,10 @@ extension type RadioNodeList._(JSObject _) implements NodeList, JSObject {
/// [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/HTMLOptionsCollection).
extension type HTMLOptionsCollection._(JSObject _)
implements HTMLCollection, JSObject {
external void operator []=(
int index,
HTMLOptionElement? option,
);
external void add(
JSObject element, [
JSAny? before,
Expand Down Expand Up @@ -1271,7 +1277,13 @@ extension type HTMLUnknownElement._(JSObject _)
///
/// API documentation sourced from
/// [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/DOMStringMap).
extension type DOMStringMap._(JSObject _) implements JSObject {}
extension type DOMStringMap._(JSObject _) implements JSObject {
external String operator [](String name);
external void operator []=(
String name,
String value,
);
}

/// The **`HTMLHtmlElement`** interface serves as the root node for a given HTML
/// document. This object inherits the properties and methods described in the
Expand Down Expand Up @@ -3728,6 +3740,8 @@ extension type MediaError._(JSObject _) implements JSObject {
/// API documentation sourced from
/// [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/AudioTrackList).
extension type AudioTrackList._(JSObject _) implements EventTarget, JSObject {
external AudioTrack operator [](int index);

/// The **[AudioTrackList]** method **`getTrackById()`** returns the first
/// [AudioTrack] object from the track list whose [AudioTrack.id] matches the
/// specified string.
Expand Down Expand Up @@ -3829,6 +3843,8 @@ extension type AudioTrack._(JSObject _) implements JSObject {
/// API documentation sourced from
/// [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/VideoTrackList).
extension type VideoTrackList._(JSObject _) implements EventTarget, JSObject {
external VideoTrack operator [](int index);

/// The **[VideoTrackList]** method
/// **`getTrackById()`** returns the first
/// [VideoTrack] object from the track list whose [VideoTrack.id] matches the
Expand Down Expand Up @@ -3949,6 +3965,8 @@ extension type VideoTrack._(JSObject _) implements JSObject {
/// API documentation sourced from
/// [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/TextTrackList).
extension type TextTrackList._(JSObject _) implements EventTarget, JSObject {
external TextTrack operator [](int index);

/// The **[TextTrackList]** method
/// **`getTrackById()`** returns the first
/// [TextTrack] object from the track list whose
Expand Down Expand Up @@ -4096,6 +4114,8 @@ extension type TextTrack._(JSObject _) implements EventTarget, JSObject {
/// API documentation sourced from
/// [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/TextTrackCueList).
extension type TextTrackCueList._(JSObject _) implements JSObject {
external TextTrackCue operator [](int index);

/// The **`getCueById()`** method of the [TextTrackCueList] interface returns
/// the first [VTTCue] in the list represented by the `TextTrackCueList`
/// object whose identifier matches the value of `id`.
Expand Down Expand Up @@ -5045,6 +5065,8 @@ extension type HTMLFormElement._(JSObject _) implements HTMLElement, JSObject {
/// Creates an [HTMLFormElement] using the tag 'form'.
HTMLFormElement() : _ = document.createElement('form');

external JSObject operator [](JSAny indexOrName);

/// The **`HTMLFormElement.submit()`** method submits a given
/// `form`.
///
Expand Down Expand Up @@ -5900,6 +5922,10 @@ extension type HTMLSelectElement._(JSObject _)
/// at the specified index from the options collection for this select
/// element.
external void remove([int index]);
external void operator []=(
int index,
HTMLOptionElement? option,
);

/// The **`HTMLSelectElement.checkValidity()`** method checks
/// whether the element has any constraints and whether it satisfies them. If
Expand Down Expand Up @@ -9823,6 +9849,8 @@ extension type DataTransfer._(JSObject _) implements JSObject {
/// API documentation sourced from
/// [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItemList).
extension type DataTransferItemList._(JSObject _) implements JSObject {
external DataTransferItem operator [](int index);

/// The **`DataTransferItemList.add()`** method creates a new
/// [DataTransferItem] using the specified data and adds it to the drag data
/// list. The item may be a [File] or a string of a
Expand Down Expand Up @@ -10136,6 +10164,7 @@ extension type Window._(JSObject _) implements EventTarget, JSObject {
String target,
String features,
]);
external JSObject operator [](String name);

/// `window.alert()` instructs the browser to display a dialog with an
/// optional message, and to wait until the user dismisses the dialog.
Expand Down
2 changes: 2 additions & 0 deletions web/lib/src/dom/media_source.dart
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,8 @@ extension type SourceBuffer._(JSObject _) implements EventTarget, JSObject {
/// API documentation sourced from
/// [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/SourceBufferList).
extension type SourceBufferList._(JSObject _) implements EventTarget, JSObject {
external SourceBuffer operator [](int index);

/// The **`length`** read-only property of the
/// [SourceBufferList] interface returns the number of
/// [SourceBuffer] objects in the list.
Expand Down
20 changes: 20 additions & 0 deletions web/lib/src/dom/svg.dart
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,10 @@ extension type SVGNumberList._(JSObject _) implements JSObject {
);
external SVGNumber removeItem(int index);
external SVGNumber appendItem(SVGNumber newItem);
external void operator []=(
int index,
SVGNumber newItem,
);
external int get length;
external int get numberOfItems;
}
Expand All @@ -534,6 +538,10 @@ extension type SVGLengthList._(JSObject _) implements JSObject {
);
external SVGLength removeItem(int index);
external SVGLength appendItem(SVGLength newItem);
external void operator []=(
int index,
SVGLength newItem,
);
external int get length;
external int get numberOfItems;
}
Expand All @@ -558,6 +566,10 @@ extension type SVGStringList._(JSObject _) implements JSObject {
);
external String removeItem(int index);
external String appendItem(String newItem);
external void operator []=(
int index,
String newItem,
);
external int get numberOfItems;
}

Expand Down Expand Up @@ -1093,6 +1105,10 @@ extension type SVGTransformList._(JSObject _) implements JSObject {
);
external SVGTransform removeItem(int index);
external SVGTransform appendItem(SVGTransform newItem);
external void operator []=(
int index,
SVGTransform newItem,
);
external SVGTransform createSVGTransformFromMatrix([DOMMatrix2DInit matrix]);
external SVGTransform? consolidate();
external int get numberOfItems;
Expand Down Expand Up @@ -1317,6 +1333,10 @@ extension type SVGPointList._(JSObject _) implements JSObject {
/// The **`appendItem()`** method of the [SVGPointList] interface adds a
/// [SVGPoint] to the end of the list.
external DOMPoint appendItem(DOMPoint newItem);
external void operator []=(
int index,
DOMPoint newItem,
);

/// The **`length`** read-only property of the [SVGPointList] interface
/// returns the number of items in the list.
Expand Down
11 changes: 11 additions & 0 deletions web/test/smoke_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,15 @@ void main() {
expect(div.style.textOverflow, equals('ellipsis'));
expect(div.style.getPropertyValue('text-overflow'), equals('ellipsis'));
});

test('External [] and []= operators work as expected.', () {
// []
expect(window['document'], window.document);
expect(window.document['body'], window.document.body);
// []=
final select = HTMLSelectElement();
final option = HTMLOptionElement();
select[0] = option;
expect(select.item(0), option);
});
}
65 changes: 50 additions & 15 deletions web_generator/lib/src/translator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,10 @@ class _RawType {
nullable = union.nullable;
typeParameter = union.typeParameter;
}

@override
String toString() => '_RawType(type: $type, nullable: $nullable, '
'typeParameter: $typeParameter)';
}

class _Parameter {
Expand Down Expand Up @@ -366,19 +370,21 @@ class _OverridableOperation extends _OverridableMember {
bool _finalized = false;
_MemberName _name;

final bool isStatic;
final String special;
final _RawType returnType;
final MdnProperty? mdnProperty;
late final _MemberName name = _generateName();

_OverridableOperation._(this._name, this.isStatic, this.returnType,
_OverridableOperation._(this._name, this.special, this.returnType,
this.mdnProperty, super.parameters);

factory _OverridableOperation(idl.Operation operation, _MemberName name,
factory _OverridableOperation(idl.Operation operation, _MemberName memberName,
MdnProperty? mdnProperty) =>
_OverridableOperation._(name, operation.special == 'static',
_OverridableOperation._(memberName, operation.special,
_getRawType(operation.idlType), mdnProperty, operation.arguments);

bool get isStatic => special == 'static';

_MemberName _generateName() {
// The name is determined after all updates are done, so finalize the
// operation.
Expand All @@ -404,7 +410,8 @@ class _OverridableOperation extends _OverridableMember {
'finalized.');
final jsOverride = _name.jsOverride;
final thisName = jsOverride.isNotEmpty ? jsOverride : _name.name;
assert(thisName == that.name && isStatic == (that.special == 'static'));
assert((that.name.isEmpty || thisName == that.name) &&
special == that.special);
returnType.update(that.idlType);
_processParameters(that.arguments);
}
Expand Down Expand Up @@ -449,7 +456,7 @@ class _PartialInterfacelike {
case 'constructor':
if (!_shouldGenerateMember(name)) break;
final idlConstructor = member as idl.Constructor;
if (_hasHTMLConstructorAttribute(idlConstructor)) continue;
if (_hasHTMLConstructorAttribute(idlConstructor)) break;
if (constructor == null) {
constructor = _OverridableConstructor(idlConstructor);
} else {
Expand Down Expand Up @@ -487,16 +494,39 @@ class _PartialInterfacelike {
break;
case 'operation':
final operation = member as idl.Operation;
final operationName = operation.name;
if (operationName.isEmpty) {
// TODO(joshualitt): We may be able to handle some unnamed
// operations.
continue;
final special = operation.special;
var operationName = operation.name;
// Some special operations may not have any MDN data and may be given
// a name that is irrelevant to the IDL, so avoid querying in that
// case and always emit.
var shouldQueryMDN = true;
switch (special) {
case 'getter':
if (operationName.isEmpty) {
operationName = 'operator []';
shouldQueryMDN = false;
}
break;
case 'setter':
if (operationName.isEmpty) {
operationName = 'operator []=';
shouldQueryMDN = false;
}
break;
case 'static':
break;
default:
// TODO(srujzs): Should we handle other special operations,
// unnamed or otherwise? For now, don't emit the unnamed ones and
// do nothing special for the named ones.
if (operationName.isEmpty) break;
}
final isStatic = operation.special == 'static';
if (!_shouldGenerateMember(operationName, isStatic: isStatic)) break;
final docs =
mdnInterface?.propertyFor(operationName, isStatic: isStatic);
if (shouldQueryMDN &&
!_shouldGenerateMember(operationName, isStatic: isStatic)) break;
final docs = shouldQueryMDN
? mdnInterface?.propertyFor(operationName, isStatic: isStatic)
: null;
// Static member may have the same name as instance members in the
// IDL, but not in Dart. Rename the static member if so.
if (isStatic) {
Expand Down Expand Up @@ -986,13 +1016,18 @@ class Translator {

code.Method _operation(_OverridableOperation operation) {
final memberName = operation.name;
// The IDL may return the value that is set. Dart doesn't let us use any
// type besides `void` for `[]=`, so we ignore the return value.
final returnType = memberName.name == 'operator []='
? code.TypeReference((b) => b..symbol = 'void')
: _typeReference(operation.returnType, returnType: true);
return _overridableMember<code.Method>(
operation,
(requiredParameters, optionalParameters) => code.Method((b) => b
..annotations.addAll(_jsOverride(memberName.jsOverride))
..external = true
..static = operation.isStatic
..returns = _typeReference(operation.returnType, returnType: true)
..returns = returnType
..name = memberName.name
..docs.addAll(operation.mdnProperty?.formattedDocs ?? [])
..requiredParameters.addAll(requiredParameters)
Expand Down

0 comments on commit 3619fd8

Please sign in to comment.