diff --git a/lib/src/builder/generation/declaration_parsing.dart b/lib/src/builder/generation/declaration_parsing.dart index 7ffdc2fe4..fd915fb3a 100644 --- a/lib/src/builder/generation/declaration_parsing.dart +++ b/lib/src/builder/generation/declaration_parsing.dart @@ -106,6 +106,7 @@ class ParsedDeclarations { Map> declarationMap = { key_factory: [], key_component: [], + key_component2: [], key_props: [], key_state: [], key_abstractComponent: [], @@ -226,15 +227,17 @@ class ParsedDeclarations { [ key_component, + key_component2, key_props, key_state, key_abstractComponent, + key_abstractComponent2, key_abstractProps, key_abstractState, key_propsMixin, key_stateMixin, ].forEach((annotationName) { - declarationMap[annotationName] = classesOnly(annotationName, declarationMap[annotationName]); + declarationMap[annotationName] = classesOnly(annotationName, declarationMap[annotationName] ?? const []); }); // Validate that all the declarations that make up a component are used correctly. @@ -242,11 +245,16 @@ class ParsedDeclarations { Iterable> requiredDecls = key_allComponentRequired.map((annotationName) => declarationMap[annotationName]); + Iterable> requiredDecls2 = + key_allComponent2Required.map((annotationName) => declarationMap[annotationName]); + Iterable> optionalDecls = key_allComponentOptional.map((annotationName) => declarationMap[annotationName]); - bool oneOfEachRequiredDecl = requiredDecls.every((decls) => decls.length == 1); - bool noneOfAnyRequiredDecl = requiredDecls.every((decls) => decls.length == 0); + bool oneOfEachRequiredDecl2 = requiredDecls2.every((decls) => decls.length == 1); + bool oneOfEachRequiredDecl = requiredDecls.every((decls) => decls.length == 1) || oneOfEachRequiredDecl2; + bool noneOfAnyRequiredDecl2 = requiredDecls2.every((decls) => decls.length == 0); + bool noneOfAnyRequiredDecl = requiredDecls.every((decls) => decls.length == 0) && noneOfAnyRequiredDecl2; bool atMostOneOfEachOptionalDecl = optionalDecls.every((decls) => decls.length <= 1); bool noneOfAnyOptionalDecl = optionalDecls.every((decls) => decls.length == 0); @@ -258,9 +266,42 @@ class ParsedDeclarations { // Give the consumer some useful errors if the declarations aren't valid. + void _emitDuplicateDeclarationError(String annotationName, int instanceNumber) { + final declarations = declarationMap[annotationName]; + error( + 'To define a component, there must be a single `@$annotationName` per file, ' + 'but ${declarations.length} were found. (${instanceNumber + 1} of ${declarations.length})', + getSpan(sourceFile, declarations[instanceNumber]) + ); + } + + if (declarationMap[key_component].isNotEmpty && declarationMap[key_component2].isNotEmpty) { + error( + 'To define a component, there must be a single `@$key_component` **OR** `@$key_component2` annotation, ' + 'but never both.' + ); + } + if (!areDeclarationsValid) { if (!noneOfAnyRequiredDecl) { - key_allComponentRequired.forEach((annotationName) { + if (declarationMap[key_component].isEmpty && declarationMap[key_component2].isEmpty) { + // Can't tell if they were trying to build a version 1 or version 2 component, + // so we'll tailor the error message accordingly. + error( + 'To define a component, there must also be a `@$key_component` or `@$key_component2` within the same file, ' + 'but none were found.' + ); + } else if (declarationMap[key_component].length > 1) { + for (int i = 0; i < declarationMap[key_component].length; i++) { + _emitDuplicateDeclarationError(key_component, i); + } + } else if (declarationMap[key_component2].length > 1) { + for (int i = 0; i < declarationMap[key_component2].length; i++) { + _emitDuplicateDeclarationError(key_component2, i); + } + } + + key_allComponentVersionsRequired.forEach((annotationName) { final declarations = declarationMap[annotationName]; if (declarations.length == 0) { error( @@ -269,11 +310,7 @@ class ParsedDeclarations { ); } else if (declarations.length > 1) { for (int i = 0; i < declarations.length; i++) { - error( - 'To define a component, there must be a single `@$annotationName` per file, ' - 'but ${declarations.length} were found. (${i + 1} of ${declarations.length})', - getSpan(sourceFile, declarations[i]) - ); + _emitDuplicateDeclarationError(annotationName, i); } } declarationMap[annotationName] = []; @@ -297,7 +334,7 @@ class ParsedDeclarations { error( 'To define a component, a `@$annotationName` must be accompanied by ' 'the following annotations within the same file: ' - '${key_allComponentRequired.map((key) => '@$key').join(', ')}.', + '(@$key_component || @$key_component2), ${key_allComponentVersionsRequired.map((key) => '@$key').join(', ')}.', getSpan(sourceFile, declarations.first) ); } @@ -357,6 +394,7 @@ class ParsedDeclarations { return new ParsedDeclarations._( factory: singleOrNull(declarationMap[key_factory]), component: singleOrNull(declarationMap[key_component]), + component2: singleOrNull(declarationMap[key_component2]), props: singleOrNull(declarationMap[key_props]), state: singleOrNull(declarationMap[key_state]), @@ -377,7 +415,10 @@ class ParsedDeclarations { ParsedDeclarations._({ TopLevelVariableDeclaration factory, + // TODO: Remove when `annotations.Component` is removed in the 4.0.0 release. + @Deprecated('4.0.0') ClassDeclaration component, + ClassDeclaration component2, ClassDeclaration props, ClassDeclaration state, @@ -395,10 +436,11 @@ class ParsedDeclarations { bool hasStateCompanionClass, bool hasAbstractStateCompanionClass, }) : - this.factory = (factory == null) ? null : new FactoryNode(factory), - this.component = (component == null) ? null : new ComponentNode(component), - this.props = (props == null) ? null : new PropsNode(props, hasPropsCompanionClass), - this.state = (state == null) ? null : new StateNode(state, hasStateCompanionClass), + this.factory = (factory == null) ? null : new FactoryNode(factory), + this.component = (component == null) ? null : new ComponentNode(component), + this.component2 = (component2 == null) ? null : new Component2Node(component2), + this.props = (props == null) ? null : new PropsNode(props, hasPropsCompanionClass), + this.state = (state == null) ? null : new StateNode(state, hasStateCompanionClass), this.abstractProps = new List.unmodifiable(abstractProps.map((props) => new AbstractPropsNode(props, hasAbstractPropsCompanionClass))), this.abstractState = new List.unmodifiable(abstractState.map((state) => new AbstractStateNode(state, hasAbstractStateCompanionClass))), @@ -409,9 +451,9 @@ class ParsedDeclarations { this.declaresComponent = factory != null { assert( - ((this.factory == null && this.component == null && this.props == null) || - (this.factory != null && this.component != null && this.props != null)) && - '`factory`, `component`, and `props` must be either all null or all non-null. ' + ((this.factory == null && ((this.component ?? this.component2) == null) && this.props == null) || + (this.factory != null && ((this.component ?? this.component2) != null) && this.props != null)) && + '`factory`, `component` / `component2`, and `props` must be either all null or all non-null. ' 'Any other combination represents an invalid component declaration. ' is String ); } @@ -419,23 +461,36 @@ class ParsedDeclarations { static final String key_factory = getName(annotations.Factory); + // TODO: Remove when `annotations.Component` is removed in the 4.0.0 release. + @Deprecated('4.0.0') static final String key_component = getName(annotations.Component); + static final String key_component2 = getName(annotations.Component2); static final String key_props = getName(annotations.Props); static final String key_state = getName(annotations.State); - static final String key_abstractComponent = getName(annotations.AbstractComponent); - static final String key_abstractProps = getName(annotations.AbstractProps); - static final String key_abstractState = getName(annotations.AbstractState); + // TODO: Remove when `annotations.AbstractComponent` is removed in the 4.0.0 release. + @Deprecated('4.0.0') + static final String key_abstractComponent = getName(annotations.AbstractComponent); + static final String key_abstractComponent2 = getName(annotations.AbstractComponent2); + static final String key_abstractProps = getName(annotations.AbstractProps); + static final String key_abstractState = getName(annotations.AbstractState); static final String key_propsMixin = getName(annotations.PropsMixin); static final String key_stateMixin = getName(annotations.StateMixin); - static final List key_allComponentRequired = new List.unmodifiable([ + static final List key_allComponentVersionsRequired = new List.unmodifiable([ key_factory, - key_component, key_props, ]); + // TODO: Remove when the `@Component` annotation is removed in the 4.0.0 release. + @Deprecated('4.0.0') + static final List key_allComponentRequired = new List.unmodifiable( + new List.from(key_allComponentVersionsRequired)..add(key_component)); + + static final List key_allComponent2Required = new List.unmodifiable( + new List.from(key_allComponentVersionsRequired)..add(key_component2)); + static final List key_allComponentOptional = new List.unmodifiable([ key_state, ]); @@ -445,9 +500,11 @@ class ParsedDeclarations { [ key_factory, key_component, + key_component2, key_props, key_state, key_abstractComponent, + key_abstractComponent2, key_abstractProps, key_abstractState, key_propsMixin, @@ -462,7 +519,10 @@ class ParsedDeclarations { } final FactoryNode factory; + // TODO: Remove when `annotations.Component` is removed in the 4.0.0 release. + @Deprecated('4.0.0') final ComponentNode component; + final Component2Node component2; final PropsNode props; final StateNode state; @@ -477,29 +537,21 @@ class ParsedDeclarations { final bool declaresComponent; /// Helper function that returns the single value of a [list], or null if it is empty. - static dynamic singleOrNull(List list) => list.isNotEmpty ? list.single : null; + static singleOrNull(List list) => list.isNotEmpty ? list.single : null; } // Generic type aliases, for readability. -class ComponentNode extends NodeWithMeta { +// TODO: Remove when `annotations.Component` is removed in the 4.0.0 release. +@Deprecated('4.0.0') +class ComponentNode + extends NodeWithMeta { static const String _subtypeOfParamName = 'subtypeOf'; - /// Whether the component extends from Component2. - final bool isComponent2; - /// The value of the `subtypeOf` parameter passed in to this node's annotation. Identifier subtypeOfValue; - ComponentNode(ClassDeclaration node) - : this.isComponent2 = node.declaredElement == null - // This can be null when using non-resolved AST in tests; FIXME 3.0.0-wip do we need to update that setup? - ? false - // TODO 3.0.0-wip is there a better way to check against react's Component2? - : node.declaredElement.allSupertypes.any((type) { - return type.name == 'Component2'; - }), - super(node) { + ComponentNode(AnnotatedNode unit) : super(unit) { // Perform special handling for the `subtypeOf` parameter of this node's annotation. // // If valid, omit it from `unsupportedArguments` so that the `meta` can be accessed without it @@ -520,6 +572,10 @@ class ComponentNode extends NodeWithMeta { + Component2Node(AnnotatedNode unit) : super(unit); +} + class FactoryNode extends NodeWithMeta {FactoryNode(unit) : super(unit);} class PropsOrStateNode extends NodeWithMeta { diff --git a/lib/src/builder/generation/impl_generation.dart b/lib/src/builder/generation/impl_generation.dart index 2acd9b053..5a6b38b7e 100644 --- a/lib/src/builder/generation/impl_generation.dart +++ b/lib/src/builder/generation/impl_generation.dart @@ -55,6 +55,9 @@ class ImplGenerator { generate(ParsedDeclarations declarations) { if (declarations.declaresComponent) { + final bool isComponent2 = declarations.component2 != null; + final componentDeclNode = isComponent2 ? declarations.component2 : declarations.component; + final factoryName = declarations.factory.node.variables.variables.first.name.toString(); final consumerPropsName = declarations.props.node.name.toString(); @@ -63,7 +66,7 @@ class ImplGenerator { final propsImplName = _propsImplClassNameFromConsumerClassName(consumerPropsName); final propsAccessorsMixinName = _accessorsMixinNameFromConsumerName(consumerPropsName); - final componentClassName = declarations.component.node.name.toString(); + final componentClassName = componentDeclNode.node.name.toString(); final componentClassImplMixinName = '$privateSourcePrefix$componentClassName'; final generatedComponentFactoryName = _componentFactoryName(componentClassName); @@ -78,7 +81,7 @@ class ImplGenerator { String parentTypeParam = 'null'; String parentTypeParamComment = ''; - Identifier parentType = declarations.component.subtypeOfValue; + Identifier parentType = componentDeclNode.subtypeOfValue; if (parentType != null) { parentTypeParamComment = ' /* from `subtypeOf: ${getSpan(sourceFile, parentType).text}` */'; @@ -99,7 +102,7 @@ class ImplGenerator { /// if a component's factory variable tries to reference itself during its initialization. /// Therefore, this is not allowed. logger.severe(messageWithSpan('A component cannot be a subtype of itself.', - span: getSpan(sourceFile, declarations.component.metaNode)) + span: getSpan(sourceFile, componentDeclNode.metaNode)) ); } @@ -110,7 +113,7 @@ class ImplGenerator { ..writeln('final $generatedComponentFactoryName = registerComponent(() => new $componentClassImplMixinName(),') ..writeln(' builderFactory: $factoryName,') ..writeln(' componentClass: $componentClassName,') - ..writeln(' isWrapper: ${declarations.component.meta.isWrapper},') + ..writeln(' isWrapper: ${componentDeclNode.meta.isWrapper},') ..writeln(' parentType: $parentTypeParam,$parentTypeParamComment') ..writeln(' displayName: ${stringLiteral(factoryName)}') ..writeln(');') @@ -131,7 +134,8 @@ class ImplGenerator { outputContentsBuffer.write( '$propsImplName $privateSourcePrefix$factoryName([Map backingProps]) => '); - if (!declarations.component.isComponent2) { + + if (!isComponent2) { /// _$$FooProps _$Foo([Map backingProps]) => new _$$FooProps(backingProps); outputContentsBuffer.writeln('new $propsImplName(backingProps);'); } else { @@ -152,10 +156,10 @@ class ImplGenerator { node: declarations.props, accessorsMixinName: propsAccessorsMixinName, consumableName: consumablePropsName, - isComponent2: declarations.component.isComponent2, + isComponent2: isComponent2, )); - if (declarations.component.isComponent2) { + if (isComponent2) { final jsMapImplName = _jsMapAccessorImplClassNameFromImplClassName(propsImplName); // This implementation here is necessary so that mixin accesses aren't compiled as index$ax typedPropsFactoryImpl @@ -206,10 +210,10 @@ class ImplGenerator { node: declarations.state, accessorsMixinName: stateAccessorsMixinName, consumableName: consumableStateName, - isComponent2: declarations.component.isComponent2, + isComponent2: isComponent2, )); - if (declarations.component.isComponent2) { + if (isComponent2) { final jsMapImplName = _jsMapAccessorImplClassNameFromImplClassName(stateImplName); // This implementation here is necessary so that mixin accesses aren't compiled as index$ax typedStateFactoryImpl @@ -256,7 +260,7 @@ class ImplGenerator { 'const [${_metaConstantName(consumablePropsName)}];') ..writeln('}'); - final implementsTypedPropsStateFactory = declarations.component.node.members.any((member) => + final implementsTypedPropsStateFactory = componentDeclNode.node.members.any((member) => member is MethodDeclaration && !member.isStatic && (member.name.name == 'typedPropsFactory' || member.name.name == 'typedStateFactory') @@ -266,7 +270,7 @@ class ImplGenerator { // Can't be an error, because consumers may be implementing typedPropsFactory or typedStateFactory in their components. logger.warning(messageWithSpan( 'Components should not add their own implementions of typedPropsFactory or typedStateFactory.', - span: getSpan(sourceFile, declarations.component.node)) + span: getSpan(sourceFile, componentDeclNode.node)) ); } } diff --git a/lib/src/component_declaration/annotations.dart b/lib/src/component_declaration/annotations.dart index fd0f6d78d..a80860297 100644 --- a/lib/src/component_declaration/annotations.dart +++ b/lib/src/component_declaration/annotations.dart @@ -69,6 +69,11 @@ class State implements TypedMap { /// } /// /// Must be accompanied by a [Factory] and [Props] declaration. +/// +/// __Deprecated.__ Use the [Component2] annotation alongside `UiComponent2` / `UiStatefulComponent2` instead. +/// +/// TODO: 3.0.0-wip is it possible to ensure that this annotation is not used on a "V2" class instance at build time? +@Deprecated('4.0.0') class Component { /// Whether the component clones or passes through its children and needs to be /// treated as if it were the wrapped component when passed in to [over_react.isComponentOfType]. @@ -104,6 +109,53 @@ class Component { }); } +/// Annotation used with the `over_react` builder to declare a `UiComponent2` class for a component. +/// +/// @Component2() +/// class FooComponent extends UiComponent2 { +/// render() => Dom.div()(props.bar); +/// } +/// +/// Must be accompanied by a [Factory] and [Props] declaration. +/// +/// TODO: 3.0.0-wip is it possible to ensure that this annotation is not used on a "V1" class instance at build time? +class Component2 implements Component { + /// Whether the component clones or passes through its children and needs to be + /// treated as if it were the wrapped component when passed in to `isComponentOfType`. + @override + final bool isWrapper; + + /// The component class of this component's "parent type". + /// + /// Used to enable inheritance in component type-checking in `isComponentOfType`. + /// + /// E.g., if component `Bar` is a subtype of component `Foo`: + /// + /// @Factory() + /// UiFactory<...> Foo = _$Foo; + /// ... + /// @Component2() + /// class FooComponent ... {...} + /// + /// @Factory() + /// UiFactory<...> Bar = _$Bar; + /// ... + /// @Component2(subtypeOf: FooComponent) + /// class BarComponent ... {...} + /// + /// then: + /// + /// isComponentOfType(Bar()(), Bar); // true (due to normal type-checking) + /// isComponentOfType(Bar()(), Foo); // true (due to parent type-checking) + @override + final Type subtypeOf; + + const Component2({ + this.isWrapper: false, + this.subtypeOf + }); +} + /// Annotation used with the `over_react` builder to declare an abstract [UiProps] class for an abstract component. /// /// Props are declared as fields, which act as stubs for generated getters/setters that proxy Map key-value pairs. @@ -140,10 +192,21 @@ class AbstractState implements TypedMap { /// /// @AbstractComponent() /// abstract class QuxComponent extends UiComponent {} +/// +/// __Deprecated.__ Use the [AbstractComponent2] annotation alongside `UiComponent2` / `UiStatefulComponent2` instead. +@Deprecated('4.0.0') class AbstractComponent { const AbstractComponent(); } +/// Annotation used with the `over_react` builder to declare an abstract `UiComponent2` class for an abstract component. +/// +/// @AbstractComponent2() +/// abstract class FooComponent extends UiComponent2 {} +class AbstractComponent2 implements AbstractComponent { + const AbstractComponent2(); +} + /// Annotation used with the `over_react` builder to declare a mixin for use in a [UiProps] class. /// /// Props are declared as fields, which act as stubs for generated getters/setters that proxy Map key-value pairs. diff --git a/lib/src/component_declaration/builder_helpers.dart b/lib/src/component_declaration/builder_helpers.dart index 564b7bf66..58a705c1b 100644 --- a/lib/src/component_declaration/builder_helpers.dart +++ b/lib/src/component_declaration/builder_helpers.dart @@ -85,6 +85,9 @@ mixin _GeneratedUiStatefulComponentStubs extends component_base.UiComponent with @@ -99,6 +102,9 @@ abstract class UiComponent /// See: [component_base.UiStatefulComponent] /// /// Use with the over_react builder via the `@Component()` ([annotations.Component]) annotation. +/// +/// __Deprecated.__ Use [UiStatefulComponent2] instead. Will be removed in the `4.0.0` release. +@Deprecated('4.0.0') abstract class UiStatefulComponent extends component_base.UiStatefulComponent with @@ -124,6 +130,9 @@ mixin _GeneratedUiComponent2Stubs TProps typedPropsFactoryJs(JsBackedMap propsMap) => throw new UngeneratedError(member: #typedPropsFactoryJs); } +/// See: [component_base.UiComponent2] +/// +/// Use with the over_react builder via the `@Component2()` ([annotations.Component2]) annotation. abstract class UiComponent2 extends component_base.UiComponent2 with @@ -136,6 +145,9 @@ abstract class UiComponent2 } } +/// See: [component_base.UiStatefulComponent2] +/// +/// Use with the over_react builder via the `@Component2()` ([annotations.Component2]) annotation. abstract class UiStatefulComponent2 extends component_base.UiStatefulComponent2 with diff --git a/lib/src/component_declaration/component_base.dart b/lib/src/component_declaration/component_base.dart index 4f344a53f..cf659ae16 100644 --- a/lib/src/component_declaration/component_base.dart +++ b/lib/src/component_declaration/component_base.dart @@ -133,6 +133,9 @@ typedef TProps BuilderOnlyUiFactory(); /// } /// /// > Related: [UiStatefulComponent] +/// +/// __Deprecated.__ Use [UiComponent2] instead. Will be removed in the `4.0.0` release. +@Deprecated('4.0.0') abstract class UiComponent extends react.Component with _DisposableManagerProxy { /// The props for the non-forwarding props defined in this component. Iterable get consumedProps => null; @@ -306,6 +309,9 @@ abstract class UiComponent extends react.Component with /// )(props.children); /// } /// } +/// +/// __Deprecated.__ Use [UiStatefulComponent2] instead. Will be removed in the `4.0.0` release. +@Deprecated('4.0.0') abstract class UiStatefulComponent extends UiComponent { // ---------------------------------------------------------------------- // ---------------------------------------------------------------------- diff --git a/lib/src/component_declaration/component_base/component_base_2.dart b/lib/src/component_declaration/component_base/component_base_2.dart index a152020a9..58c974d69 100644 --- a/lib/src/component_declaration/component_base/component_base_2.dart +++ b/lib/src/component_declaration/component_base/component_base_2.dart @@ -14,7 +14,49 @@ part of over_react.component_declaration.component_base; -// FIXME 3.0.0-wip update doc comment +/// The basis for an over_react component that is compatible with ReactJS 16 ([react.Component2]). +/// +/// Includes support for strongly-typed [UiProps] and utilities for prop and CSS classname forwarding. +/// +/// __Prop and CSS className forwarding when your component renders a composite component:__ +/// +/// @Component2() +/// class YourComponent extends UiComponent2 { +/// Map getDefaultProps() => (newProps() +/// ..aPropOnYourComponent = /* default value */ +/// ); +/// +/// @override +/// render() { +/// var classes = forwardingClassNameBuilder() +/// ..add('your-component-base-class') +/// ..add('a-conditional-class', shouldApplyConditionalClass); +/// +/// return (SomeChildComponent() +/// ..addProps(copyUnconsumedProps()) +/// ..className = classes.toClassName() +/// )(props.children); +/// } +/// } +/// +/// __Prop and CSS className forwarding when your component renders a DOM component:__ +/// +/// @Component2() +/// class YourComponent extends UiComponent2 { +/// @override +/// render() { +/// var classes = forwardingClassNameBuilder() +/// ..add('your-component-base-class') +/// ..add('a-conditional-class', shouldApplyConditionalClass); +/// +/// return (Dom.div() +/// ..addProps(copyUnconsumedDomProps()) +/// ..className = classes.toClassName() +/// )(props.children); +/// } +/// } +/// +/// > Related: [UiStatefulComponent2] abstract class UiComponent2 extends react.Component2 with _DisposableManagerProxy implements UiComponent { @@ -165,7 +207,34 @@ abstract class UiComponent2 extends react.Component2 // ---------------------------------------------------------------------- } -// FIXME 3.0.0-wip update doc comment +/// The basis for a _stateful_ over_react component that is compatible with ReactJS 16 ([react.Component2]). +/// +/// Includes support for strongly-typed [UiState] in-addition-to the +/// strongly-typed props and utilities for prop and CSS classname forwarding provided by [UiComponent]. +/// +/// __Initializing state:__ +/// +/// @Component2() +/// class YourComponent extends UiStatefulComponent2 { +/// @override +/// void init() { +/// this.state = (newState() +/// ..aStateKeyWithinYourStateClass = /* default value */ +/// ); +/// } +/// +/// @override +/// render() { +/// var classes = forwardingClassNameBuilder() +/// ..add('your-component-base-class') +/// ..add('a-conditional-class', state.aStateKeyWithinYourStateClass); +/// +/// return (SomeChildComponent() +/// ..addProps(copyUnconsumedProps()) +/// ..className = classes.toClassName() +/// )(props.children); +/// } +/// } abstract class UiStatefulComponent2 extends UiComponent2 implements UiStatefulComponent { diff --git a/lib/src/component_declaration/component_type_checking.dart b/lib/src/component_declaration/component_type_checking.dart index fb37e5774..ba1c32169 100644 --- a/lib/src/component_declaration/component_type_checking.dart +++ b/lib/src/component_declaration/component_type_checking.dart @@ -18,7 +18,7 @@ library over_react.component_declaration.component_type_checking; import 'dart:js_util'; import 'package:over_react/src/component_declaration/component_base.dart' show UiFactory; -import 'package:over_react/src/component_declaration/annotations.dart' as annotations show Component; +import 'package:over_react/src/component_declaration/annotations.dart' as annotations show Component2; import 'package:over_react/src/util/react_wrappers.dart'; import 'package:react/react_client.dart'; import 'package:react/react_client/react_interop.dart'; @@ -89,8 +89,8 @@ class ComponentTypeMeta { /// @Factory() /// UiFactory Foo; /// - /// @Component() - /// class FooComponent extends UiComponent { + /// @Component2() + /// class FooComponent extends UiComponent2 { /// // ... /// } /// @@ -101,8 +101,8 @@ class ComponentTypeMeta { /// @Factory() /// UiFactory Foo; /// - /// @Component(subtypeOf: FooComponent) - /// class BarComponent extends UiComponent { + /// @Component2(subtypeOf: FooComponent) + /// class BarComponent extends UiComponent2 { /// // ... /// } /// @@ -113,7 +113,8 @@ class ComponentTypeMeta { /// isComponentOfType(Bar()(), Bar); // true (due to normal type-checking) /// isComponentOfType(Bar()(), Foo); // true (due to parent type-checking) /// - /// > See: `subtypeOf` (within [annotations.Component]) + /// > See: `subtypeOf` (within [annotations.Component2]) + /// TODO: 3.0.0-wip does this need to have a different type signature for Component2? // ignore: deprecated_member_use final ReactDartComponentFactoryProxy parentType; diff --git a/lib/src/component_declaration/flux_component.dart b/lib/src/component_declaration/flux_component.dart index da8dbf24b..61d0c2ce2 100644 --- a/lib/src/component_declaration/flux_component.dart +++ b/lib/src/component_declaration/flux_component.dart @@ -68,6 +68,7 @@ abstract class FluxUiProps extends UiProps { /// Use with the over_react builder via the `@Component()` ([annotations.Component]) annotation. /// /// > Related: [FluxUiStatefulComponent] +/// FIXME: 3.0.0-wip We need to deprecate this, and create a FluxUiComponent2 class abstract class FluxUiComponent extends UiComponent with _FluxComponentMixin, BatchedRedraws { // Redeclare these lifecycle methods with `mustCallSuper`, since `mustCallSuper` added to methods within @@ -108,6 +109,7 @@ abstract class FluxUiComponent extends UiComponent Related: [FluxUiComponent] +/// FIXME: 3.0.0-wip We need to deprecate this, and create a FluxUiStatefulComponent2 class abstract class FluxUiStatefulComponent extends UiStatefulComponent with _FluxComponentMixin, BatchedRedraws { diff --git a/lib/src/util/react_wrappers.dart b/lib/src/util/react_wrappers.dart index e3a6dc847..96edb09c7 100644 --- a/lib/src/util/react_wrappers.dart +++ b/lib/src/util/react_wrappers.dart @@ -156,7 +156,6 @@ Map getProps(/* ReactElement|ReactComponent */ instance, {bool traverseWrappers: } /// Returns the DOM node associated with a mounted React component [instance], -// ignore: deprecated_member_use /// which can be a [ReactComponent]/[Element] or [react.Component]. /// /// This method simply wraps react.findDOMNode with strong typing for the return value @@ -166,7 +165,7 @@ Element findDomNode(dynamic instance) => react_dom.findDOMNode(instance); /// Dart wrapper for React.isValidElement. /// /// _From the JS docs:_ -/// > Verifies the object is a ReactElement +/// > Verifies the [object] is a ReactElement bool isValidElement(dynamic object) { return React.isValidElement(object); } @@ -179,8 +178,8 @@ bool isDomElement(dynamic instance) { /// Returns whether [instance] is a composite [ReactComponent]. /// /// __Not for external use.__ -bool _isCompositeComponent(dynamic object) { - return object != null && getProperty(object, 'isReactComponent') != null; +bool _isCompositeComponent(dynamic instance) { + return instance != null && getProperty(instance, 'isReactComponent') != null; } /// Returns a new JS map with the specified props and children changes, properly prepared for consumption by @@ -189,7 +188,6 @@ bool _isCompositeComponent(dynamic object) { /// /// Handles both Dart and JS React components, returning the appropriate props structure for each type: /// -// ignore: deprecated_member_use /// * For non-[react.Component2] Dart components, existing props are read from [InteropProps.internal], which are then merged with /// the new [newProps] and saved in a new [InteropProps] with the expected [ReactDartComponentInternal] structure. /// * For [react.Component2] Dart components, [newProps] is passed through [ReactDartComponentFactoryProxy2.generateExtendedJsProps] diff --git a/test/over_react/component_declaration/builder_integration_tests/component2/abstract_accessor_integration_test.over_react.g.dart b/test/over_react/component_declaration/builder_integration_tests/component2/abstract_accessor_integration_test.over_react.g.dart index edfd22f40..3733802ae 100644 --- a/test/over_react/component_declaration/builder_integration_tests/component2/abstract_accessor_integration_test.over_react.g.dart +++ b/test/over_react/component_declaration/builder_integration_tests/component2/abstract_accessor_integration_test.over_react.g.dart @@ -3,7 +3,7 @@ part of 'abstract_accessor_integration_test.dart'; // ************************************************************************** -// OverReactGenerator +// OverReactBuilder (package:over_react/src/builder.dart) // ************************************************************************** abstract class _$TestAbstractPropsAccessorsMixin diff --git a/test/over_react/component_declaration/builder_integration_tests/component2/accessor_mixin_integration_test.over_react.g.dart b/test/over_react/component_declaration/builder_integration_tests/component2/accessor_mixin_integration_test.over_react.g.dart index 165457b4b..2d67833bb 100644 --- a/test/over_react/component_declaration/builder_integration_tests/component2/accessor_mixin_integration_test.over_react.g.dart +++ b/test/over_react/component_declaration/builder_integration_tests/component2/accessor_mixin_integration_test.over_react.g.dart @@ -3,7 +3,7 @@ part of 'accessor_mixin_integration_test.dart'; // ************************************************************************** -// OverReactGenerator +// OverReactBuilder (package:over_react/src/builder.dart) // ************************************************************************** abstract class TestPropsMixin implements _$TestPropsMixin { diff --git a/test/over_react/component_declaration/builder_integration_tests/component2/component_integration_test.dart b/test/over_react/component_declaration/builder_integration_tests/component2/component_integration_test.dart index 7ef38671c..65864e19c 100644 --- a/test/over_react/component_declaration/builder_integration_tests/component2/component_integration_test.dart +++ b/test/over_react/component_declaration/builder_integration_tests/component2/component_integration_test.dart @@ -141,7 +141,7 @@ class _$ComponentTestProps extends UiProps { var customKeyAndNamespaceProp; } -@Component() +@Component2() class ComponentTestComponent extends UiComponent2 { @override Map getDefaultProps() => newProps()..id = 'testId'; diff --git a/test/over_react/component_declaration/builder_integration_tests/component2/component_integration_test.over_react.g.dart b/test/over_react/component_declaration/builder_integration_tests/component2/component_integration_test.over_react.g.dart index efe8a003d..b0ede5192 100644 --- a/test/over_react/component_declaration/builder_integration_tests/component2/component_integration_test.over_react.g.dart +++ b/test/over_react/component_declaration/builder_integration_tests/component2/component_integration_test.over_react.g.dart @@ -3,7 +3,7 @@ part of 'component_integration_test.dart'; // ************************************************************************** -// OverReactGenerator +// OverReactBuilder (package:over_react/src/builder.dart) // ************************************************************************** // React component factory implementation. diff --git a/test/over_react/component_declaration/builder_integration_tests/component2/constant_required_accessor_integration_test.dart b/test/over_react/component_declaration/builder_integration_tests/component2/constant_required_accessor_integration_test.dart index 8c3db62d7..e20f32425 100644 --- a/test/over_react/component_declaration/builder_integration_tests/component2/constant_required_accessor_integration_test.dart +++ b/test/over_react/component_declaration/builder_integration_tests/component2/constant_required_accessor_integration_test.dart @@ -146,7 +146,7 @@ class _$ComponentTestProps extends UiProps { var nullable; } -@Component() +@Component2() class ComponentTestComponent extends UiComponent2 { @override render() => Dom.div()(); diff --git a/test/over_react/component_declaration/builder_integration_tests/component2/do_not_generate_accessor_integration_test.dart b/test/over_react/component_declaration/builder_integration_tests/component2/do_not_generate_accessor_integration_test.dart index e18523b44..6b2622b33 100644 --- a/test/over_react/component_declaration/builder_integration_tests/component2/do_not_generate_accessor_integration_test.dart +++ b/test/over_react/component_declaration/builder_integration_tests/component2/do_not_generate_accessor_integration_test.dart @@ -112,7 +112,7 @@ class _$DoNotGenerateAccessorTestState extends UiState { var explicitlyGeneratedState; } -@Component() +@Component2() class DoNotGenerateAccessorTestComponent extends UiStatefulComponent2 { @override render() => (Dom.div() diff --git a/test/over_react/component_declaration/builder_integration_tests/component2/do_not_generate_accessor_integration_test.over_react.g.dart b/test/over_react/component_declaration/builder_integration_tests/component2/do_not_generate_accessor_integration_test.over_react.g.dart index f626c64b7..60a9bbe6c 100644 --- a/test/over_react/component_declaration/builder_integration_tests/component2/do_not_generate_accessor_integration_test.over_react.g.dart +++ b/test/over_react/component_declaration/builder_integration_tests/component2/do_not_generate_accessor_integration_test.over_react.g.dart @@ -3,7 +3,7 @@ part of 'do_not_generate_accessor_integration_test.dart'; // ************************************************************************** -// OverReactGenerator +// OverReactBuilder (package:over_react/src/builder.dart) // ************************************************************************** // React component factory implementation. diff --git a/test/over_react/component_declaration/builder_integration_tests/component2/namespaced_accessor_integration_test.dart b/test/over_react/component_declaration/builder_integration_tests/component2/namespaced_accessor_integration_test.dart index d5f817192..5e1660d97 100644 --- a/test/over_react/component_declaration/builder_integration_tests/component2/namespaced_accessor_integration_test.dart +++ b/test/over_react/component_declaration/builder_integration_tests/component2/namespaced_accessor_integration_test.dart @@ -137,7 +137,7 @@ class _$NamespacedAccessorTestState extends UiState { var customKeyAndNamespaceState; } -@Component() +@Component2() class NamespacedAccessorTestComponent extends UiStatefulComponent2 { @override render() => (Dom.div() diff --git a/test/over_react/component_declaration/builder_integration_tests/component2/namespaced_accessor_integration_test.over_react.g.dart b/test/over_react/component_declaration/builder_integration_tests/component2/namespaced_accessor_integration_test.over_react.g.dart index 2873725c0..17c5388a1 100644 --- a/test/over_react/component_declaration/builder_integration_tests/component2/namespaced_accessor_integration_test.over_react.g.dart +++ b/test/over_react/component_declaration/builder_integration_tests/component2/namespaced_accessor_integration_test.over_react.g.dart @@ -3,7 +3,7 @@ part of 'namespaced_accessor_integration_test.dart'; // ************************************************************************** -// OverReactGenerator +// OverReactBuilder (package:over_react/src/builder.dart) // ************************************************************************** // React component factory implementation. diff --git a/test/over_react/component_declaration/builder_integration_tests/component2/private_props_ddc_bug.dart b/test/over_react/component_declaration/builder_integration_tests/component2/private_props_ddc_bug.dart index ac9775074..1dffc5a91 100644 --- a/test/over_react/component_declaration/builder_integration_tests/component2/private_props_ddc_bug.dart +++ b/test/over_react/component_declaration/builder_integration_tests/component2/private_props_ddc_bug.dart @@ -26,7 +26,7 @@ class _$FooProps extends UiProps { String _privateProp; } -@Component() +@Component2() class FooComponent extends UiComponent2 { @override Map getDefaultProps() => newProps().._privateProp = 'some private value'; diff --git a/test/over_react/component_declaration/builder_integration_tests/component2/private_props_ddc_bug.over_react.g.dart b/test/over_react/component_declaration/builder_integration_tests/component2/private_props_ddc_bug.over_react.g.dart index 06dafe217..1b65c5814 100644 --- a/test/over_react/component_declaration/builder_integration_tests/component2/private_props_ddc_bug.over_react.g.dart +++ b/test/over_react/component_declaration/builder_integration_tests/component2/private_props_ddc_bug.over_react.g.dart @@ -3,7 +3,7 @@ part of 'private_props_ddc_bug.dart'; // ************************************************************************** -// OverReactGenerator +// OverReactBuilder (package:over_react/src/builder.dart) // ************************************************************************** // React component factory implementation. diff --git a/test/over_react/component_declaration/builder_integration_tests/component2/required_accessor_integration_test.dart b/test/over_react/component_declaration/builder_integration_tests/component2/required_accessor_integration_test.dart index 640c66640..0eca72632 100644 --- a/test/over_react/component_declaration/builder_integration_tests/component2/required_accessor_integration_test.dart +++ b/test/over_react/component_declaration/builder_integration_tests/component2/required_accessor_integration_test.dart @@ -146,7 +146,7 @@ class _$ComponentTestProps extends UiProps { var nullable; } -@Component() +@Component2() class ComponentTestComponent extends UiComponent2 { @override render() => Dom.div()(); diff --git a/test/over_react/component_declaration/builder_integration_tests/component2/stateful_component_integration_test.dart b/test/over_react/component_declaration/builder_integration_tests/component2/stateful_component_integration_test.dart index 582988541..c8df7b8d0 100644 --- a/test/over_react/component_declaration/builder_integration_tests/component2/stateful_component_integration_test.dart +++ b/test/over_react/component_declaration/builder_integration_tests/component2/stateful_component_integration_test.dart @@ -100,7 +100,7 @@ class _$StatefulComponentTestState extends UiState { var customKeyAndNamespaceState; } -@Component() +@Component2() class StatefulComponentTestComponent extends UiStatefulComponent2 { @override getInitialState() => (newState() diff --git a/test/over_react/component_declaration/builder_integration_tests/component2/stateful_component_integration_test.over_react.g.dart b/test/over_react/component_declaration/builder_integration_tests/component2/stateful_component_integration_test.over_react.g.dart index c81eec302..6cd657b6d 100644 --- a/test/over_react/component_declaration/builder_integration_tests/component2/stateful_component_integration_test.over_react.g.dart +++ b/test/over_react/component_declaration/builder_integration_tests/component2/stateful_component_integration_test.over_react.g.dart @@ -3,7 +3,7 @@ part of 'stateful_component_integration_test.dart'; // ************************************************************************** -// OverReactGenerator +// OverReactBuilder (package:over_react/src/builder.dart) // ************************************************************************** // React component factory implementation. diff --git a/test/over_react/component_declaration/builder_integration_tests/component2/unassigned_prop_integration_test.dart b/test/over_react/component_declaration/builder_integration_tests/component2/unassigned_prop_integration_test.dart index a52ab015e..7abcaa64e 100644 --- a/test/over_react/component_declaration/builder_integration_tests/component2/unassigned_prop_integration_test.dart +++ b/test/over_react/component_declaration/builder_integration_tests/component2/unassigned_prop_integration_test.dart @@ -32,7 +32,7 @@ class _$FooProps extends UiProps { String unassignedProp; } -@Component() +@Component2() class FooComponent extends UiComponent2 { @override Map getDefaultProps() => newProps()..id = 'testId'; diff --git a/test/over_react/component_declaration/builder_integration_tests/component2/unassigned_prop_integration_test.over_react.g.dart b/test/over_react/component_declaration/builder_integration_tests/component2/unassigned_prop_integration_test.over_react.g.dart index eb9c0d6e1..fa43077aa 100644 --- a/test/over_react/component_declaration/builder_integration_tests/component2/unassigned_prop_integration_test.over_react.g.dart +++ b/test/over_react/component_declaration/builder_integration_tests/component2/unassigned_prop_integration_test.over_react.g.dart @@ -3,7 +3,7 @@ part of 'unassigned_prop_integration_test.dart'; // ************************************************************************** -// OverReactGenerator +// OverReactBuilder (package:over_react/src/builder.dart) // ************************************************************************** // React component factory implementation. diff --git a/test/over_react/component_declaration/redux_component_test/test_reducer.g.dart b/test/over_react/component_declaration/redux_component_test/test_reducer.g.dart index 43a6321f9..fa830b482 100644 --- a/test/over_react/component_declaration/redux_component_test/test_reducer.g.dart +++ b/test/over_react/component_declaration/redux_component_test/test_reducer.g.dart @@ -42,7 +42,7 @@ class _$BaseState extends BaseState { @override final int count2; - factory _$BaseState([void updates(BaseStateBuilder b)]) => + factory _$BaseState([void Function(BaseStateBuilder) updates]) => (new BaseStateBuilder()..update(updates)).build(); _$BaseState._({this.count1, this.count2}) : super._() { @@ -55,7 +55,7 @@ class _$BaseState extends BaseState { } @override - BaseState rebuild(void updates(BaseStateBuilder b)) => + BaseState rebuild(void Function(BaseStateBuilder) updates) => (toBuilder()..update(updates)).build(); @override @@ -114,7 +114,7 @@ class BaseStateBuilder implements Builder { } @override - void update(void updates(BaseStateBuilder b)) { + void update(void Function(BaseStateBuilder) updates) { if (updates != null) updates(this); } diff --git a/test/vm_tests/builder/declaration_parsing_test.dart b/test/vm_tests/builder/declaration_parsing_test.dart index b249bd4e9..250cb1156 100644 --- a/test/vm_tests/builder/declaration_parsing_test.dart +++ b/test/vm_tests/builder/declaration_parsing_test.dart @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// ignore_for_file: deprecated_member_use_from_same_package + @TestOn('vm') library declaration_parsing_test; @@ -34,9 +36,11 @@ main() { test('"@Props"', () => expect(mightContainDeclarations(propsSrc), isTrue)); test('"@State"', () => expect(mightContainDeclarations(stateSrc), isTrue)); test('"@Component"', () => expect(mightContainDeclarations(componentSrc), isTrue)); + test('"@Component2"', () => expect(mightContainDeclarations(component2Src), isTrue)); test('"@AbstractProps"', () => expect(mightContainDeclarations(abstractPropsSrc), isTrue)); test('"@AbstractState"', () => expect(mightContainDeclarations(abstractStateSrc), isTrue)); test('"@AbstractComponent"', () => expect(mightContainDeclarations(abstractComponentSrc), isTrue)); + test('"@AbstractComponent2"',() => expect(mightContainDeclarations(abstractComponent2Src), isTrue)); test('"@PropsMixin"', () => expect(mightContainDeclarations(propsMixinSrc), isTrue)); test('"@StateMixin"', () => expect(mightContainDeclarations(stateMixinSrc), isTrue)); }); @@ -45,11 +49,17 @@ main() { expect(mightContainDeclarations('class FooComponent extends UiComponent {}'), isFalse, reason: 'should not return true for an unannotated class'); + expect(mightContainDeclarations('class FooComponent extends UiComponent2 {}'), isFalse, + reason: 'should not return true for an unannotated class'); + expect(mightContainDeclarations('@Bar\nclass Foo {}'), isFalse, reason: 'should not return true for a class with non-matching annotations'); expect(mightContainDeclarations('/// Component that...\nclass Foo {}'), isFalse, reason: 'should not return true when an annotation class name is not used as an annotation'); + + expect(mightContainDeclarations('/// Component2 that...\nclass Foo {}'), isFalse, + reason: 'should not return true when an annotation class name is not used as an annotation'); }); }); @@ -86,6 +96,7 @@ main() { props: true, state: true, component: true, + component2: true, abstractProps: true, abstractState: true, propsMixins: true, @@ -96,6 +107,7 @@ main() { expect(declarations.props, props ? isNull : isNotNull, reason: reason); expect(declarations.state, state ? isNull : isNotNull, reason: reason); expect(declarations.component, component ? isNull : isNotNull, reason: reason); + expect(declarations.component2, component2 ? isNull : isNotNull, reason: reason); expect(declarations.abstractProps, abstractProps ? isEmpty : isNotEmpty, reason: reason); expect(declarations.abstractState, abstractState ? isEmpty : isNotEmpty, reason: reason); expect(declarations.propsMixins, propsMixins ? isEmpty : isNotEmpty, reason: reason); @@ -114,78 +126,133 @@ main() { expect(declarations.declaresComponent, isFalse); }); - group('a component ', () { - void testPropsDualClassSetup({bool backwardsCompatible: true, bool isPrivate: false}) { - final ors = OverReactSrc.props(backwardsCompatible: backwardsCompatible, isPrivate: isPrivate); + group('a component', () { + void testDualClassSetup({ + bool backwardsCompatible: true, + bool isPrivate: false, + bool isStatefulComponent: false, + int componentVersion: 1, + }) { + OverReactSrc ors; + if (isStatefulComponent) { + ors = OverReactSrc.state( + backwardsCompatible: backwardsCompatible, + isPrivate: isPrivate, + componentVersion: componentVersion); + } else { + ors = OverReactSrc.props( + backwardsCompatible: backwardsCompatible, + isPrivate: isPrivate, + componentVersion: componentVersion); + } setUpAndParse(ors.source); + expect(declarations.declaresComponent, isTrue); expect(declarations.factory.node?.variables?.variables?.single?.name ?.name, ors.baseName); expect(declarations.props.node?.name?.name, '_\$${ors.baseName}Props'); - expect(declarations.component.node?.name?.name, '${ors.baseName}Component'); - expect(declarations.factory.meta, const TypeMatcher()); - expect(declarations.props.meta, const TypeMatcher()); - expect(declarations.component.meta, const TypeMatcher()); + expect(declarations.factory.meta, const TypeMatcher()); + expect(declarations.props.meta, const TypeMatcher()); - expectEmptyDeclarations( - factory: false, props: false, component: false); - expect(declarations.declaresComponent, isTrue); + if (isStatefulComponent) { + expect(declarations.state.node?.name?.name, '_\$${ors.baseName}State'); + expect(declarations.state.meta, const TypeMatcher()); + } + + if (componentVersion == 1) { + expect(declarations.component.node?.name?.name, '${ors.baseName}Component'); + expect(declarations.component.meta, const TypeMatcher()); + expectEmptyDeclarations(factory: false, props: false, state: !isStatefulComponent, component: false); + } else if (componentVersion == 2) { + expect(declarations.component2.node?.name?.name, '${ors.baseName}Component'); + expect(declarations.component2.meta, const TypeMatcher()); + expectEmptyDeclarations(factory: false, props: false, state: !isStatefulComponent, component2: false); + } } - group('with backwards compatible boilerplate', () { - test('with public consumable class', () { - testPropsDualClassSetup(); - }); - test('with private consumable class', () { - testPropsDualClassSetup(isPrivate: true); - }); - }); + group('that is stateless', () { + group('(v1 - deprecated)', () { + group('with backwards compatible boilerplate', () { + test('with public consumable class', () { + testDualClassSetup(); + }); + test('with private consumable class', () { + testDualClassSetup(isPrivate: true); + }); + }); - group('with Dart 2 only boilerplate', () { - test('with public consumable class', () { - testPropsDualClassSetup(backwardsCompatible: false); - }); - test('with private consumable class', () { - testPropsDualClassSetup(backwardsCompatible: false, isPrivate: true); + group('with Dart 2 only boilerplate', () { + test('with public consumable class', () { + testDualClassSetup(backwardsCompatible: false); + }); + test('with private consumable class', () { + testDualClassSetup(backwardsCompatible: false, isPrivate: true); + }); + }); }); - }); - }); - - group('a stateful component', () { - void testStateDualClassSetup({bool backwardsCompatible: true, bool isPrivate: false}) { - final ors = OverReactSrc.state(backwardsCompatible: backwardsCompatible, isPrivate: isPrivate); - setUpAndParse(ors.source); - - expect(declarations.factory.node?.variables?.variables?.single?.name?.name, ors.baseName); - expect(declarations.props.node?.name?.name, '_\$${ors.baseName}Props'); - expect(declarations.state.node?.name?.name, '_\$${ors.baseName}State'); - expect(declarations.component.node?.name?.name, '${ors.baseName}Component'); - expect(declarations.factory.meta, const TypeMatcher()); - expect(declarations.props.meta, const TypeMatcher()); - expect(declarations.state.meta, const TypeMatcher()); - expect(declarations.component.meta, const TypeMatcher()); - - expectEmptyDeclarations(factory: false, props: false, state: false, component: false); - expect(declarations.declaresComponent, isTrue); - } + group('(v2)', () { + group('with backwards compatible boilerplate', () { + test('with public consumable class', () { + testDualClassSetup(componentVersion: 2); + }); + test('with private consumable class', () { + testDualClassSetup(componentVersion: 2, isPrivate: true); + }); + }); - group('with backwards compatible boilerplate', () { - test('with public consumable class', () { - testStateDualClassSetup(); - }); - test('with private consumable class', () { - testStateDualClassSetup(isPrivate: true); + group('with Dart 2 only boilerplate', () { + test('with public consumable class', () { + testDualClassSetup(componentVersion: 2, backwardsCompatible: false); + }); + test('with private consumable class', () { + testDualClassSetup(componentVersion: 2, backwardsCompatible: false, isPrivate: true); + }); + }); }); }); - group('with Dart 2 only boilerplate', () { - test('with public consumable class', () { - testStateDualClassSetup(backwardsCompatible: false); + group('that is stateful', () { + group('and uses the @Component annotation (deprecated)', () { + group('with backwards compatible boilerplate', () { + test('with public consumable class', () { + testDualClassSetup(isStatefulComponent: true); + }); + test('with private consumable class', () { + testDualClassSetup(isStatefulComponent: true, isPrivate: true); + }); + }); + + group('with Dart 2 only boilerplate', () { + test('with public consumable class', () { + testDualClassSetup(isStatefulComponent: true, backwardsCompatible: false); + }); + test('with private consumable class', () { + testDualClassSetup(isStatefulComponent: true, backwardsCompatible: false, isPrivate: true); + }); + }); }); - test('with private consumable class', () { - testStateDualClassSetup(backwardsCompatible: false, isPrivate: true); + + group('and uses the @Component2 annotation', () { + group('with backwards compatible boilerplate', () { + test('with public consumable class', () { + testDualClassSetup(isStatefulComponent: true, componentVersion: 2); + }); + test('with private consumable class', () { + testDualClassSetup(isStatefulComponent: true, isPrivate: true, componentVersion: 2); + }); + }); + + group('with Dart 2 only boilerplate', () { + test('with public consumable class', () { + testDualClassSetup(isStatefulComponent: true, backwardsCompatible: false, componentVersion: 2); + }); + test('with private consumable class', () { + testDualClassSetup(isStatefulComponent: true, backwardsCompatible: false, isPrivate: true, + componentVersion: 2); + }); + }); }); }); }); @@ -559,7 +626,7 @@ main() { test('a component class', () { setUpAndParse(factorySrc + propsSrc); - verify(logger.severe(contains('To define a component, there must also be a `@Component` within the same file, but none were found.'))); + verify(logger.severe(contains('To define a component, there must also be a `@Component` or `@Component2` within the same file, but none were found.'))); }); test('a factory or a props class', () { @@ -568,23 +635,34 @@ main() { verify(logger.severe(contains('To define a component, there must also be a `@Props` within the same file, but none were found.'))); }); + test('a factory or a props class (v2 component)', () { + setUpAndParse(component2Src); + verify(logger.severe(contains('To define a component, there must also be a `@Factory` within the same file, but none were found.'))); + verify(logger.severe(contains('To define a component, there must also be a `@Props` within the same file, but none were found.'))); + }); + test('a factory or a component class', () { setUpAndParse(propsSrc); verify(logger.severe(contains('To define a component, there must also be a `@Factory` within the same file, but none were found.'))); - verify(logger.severe(contains('To define a component, there must also be a `@Component` within the same file, but none were found.'))); + verify(logger.severe(contains('To define a component, there must also be a `@Component` or `@Component2` within the same file, but none were found.'))); }); test('a component or props class', () { setUpAndParse(factorySrc); - verify(logger.severe(contains('To define a component, there must also be a `@Component` within the same file, but none were found.'))); + verify(logger.severe(contains('To define a component, there must also be a `@Component` or `@Component2` within the same file, but none were found.'))); verify(logger.severe(contains('To define a component, there must also be a `@Props` within the same file, but none were found.'))); }); }); + test('both a component and component2 class', () { + setUpAndParse(factorySrc + propsSrc + componentSrc + component2Src); + verify(logger.severe(contains('To define a component, there must be a single `@Component` **OR** `@Component2` annotation, but never both.'))); + }); + group('a state class is declared without', () { test('any component pieces', () { setUpAndParse(stateSrc); - verify(logger.severe(contains('To define a component, a `@State` must be accompanied by the following annotations within the same file: @Factory, @Component, @Props.'))); + verify(logger.severe(contains('To define a component, a `@State` must be accompanied by the following annotations within the same file: (@Component || @Component2), @Factory, @Props.'))); }); test('some component pieces', () { @@ -593,36 +671,71 @@ main() { verify(logger.severe(contains('To define a component, there must also be a `@Factory` within the same file, but none were found.'))); verify(logger.severe(contains('To define a component, there must also be a `@Props` within the same file, but none were found.'))); }); + + test('some component2 pieces', () { + setUpAndParse(stateSrc + component2Src); + /// Should only log regarding the missing pieces, and not the state. + verify(logger.severe(contains('To define a component, there must also be a `@Factory` within the same file, but none were found.'))); + verify(logger.severe(contains('To define a component, there must also be a `@Props` within the same file, but none were found.'))); + }); }); group('a component is declared with multiple', () { - test('factories', () { + test('factories (v1 component - deprecated)', () { setUpAndParse(factorySrc * 2 + propsSrc + componentSrc); verify(logger.severe( argThat(startsWith('To define a component, there must be a single `@Factory` per file, but 2 were found.')) )).called(2); }); - test('props classes', () { + test('factories (v2 component)', () { + setUpAndParse(factorySrc * 2 + propsSrc + component2Src); + verify(logger.severe( + argThat(startsWith('To define a component, there must be a single `@Factory` per file, but 2 were found.')) + )).called(2); + }); + + test('props classes (v1 component - deprecated)', () { setUpAndParse(factorySrc + propsSrc * 2 + componentSrc); verify(logger.severe( argThat(startsWith('To define a component, there must be a single `@Props` per file, but 2 were found.')) )).called(2); }); - test('component classes', () { + test('props classes (v2 component)', () { + setUpAndParse(factorySrc + propsSrc * 2 + component2Src); + verify(logger.severe( + argThat(startsWith('To define a component, there must be a single `@Props` per file, but 2 were found.')) + )).called(2); + }); + + test('component classes (v1 - deprecated)', () { setUpAndParse(factorySrc + propsSrc + componentSrc * 2); verify(logger.severe( argThat(startsWith('To define a component, there must be a single `@Component` per file, but 2 were found.')) )).called(2); }); - test('state classes', () { + test('component2 classes', () { + setUpAndParse(factorySrc + propsSrc + component2Src * 2); + verify(logger.severe( + argThat(startsWith('To define a component, there must be a single `@Component2` per file, but 2 were found.')) + )).called(2); + }); + + test('state classes (v1 component - deprecated)', () { setUpAndParse(factorySrc + propsSrc + componentSrc + stateSrc * 2); verify(logger.severe( argThat(startsWith('To define a component, there must not be more than one `@State` per file, but 2 were found.')) )).called(2); }); + + test('state classes (v2 component)', () { + setUpAndParse(factorySrc + propsSrc + component2Src + stateSrc * 2); + verify(logger.severe( + argThat(startsWith('To define a component, there must not be more than one `@State` per file, but 2 were found.')) + )).called(2); + }); }); group('an annotation is used on the wrong kind of declaration:', () { @@ -641,6 +754,11 @@ main() { verify(logger.severe(contains('`@Component` can only be used on classes.'))); }); + test('@Component2 on a non-class', () { + setUpAndParse('@Component2() var notAClass;'); + verify(logger.severe(contains('`@Component2` can only be used on classes.'))); + }); + test('@State on a non-class', () { setUpAndParse('@Props() var notAClass;'); verify(logger.severe(contains('`@Props` can only be used on classes.'))); @@ -725,7 +843,7 @@ main() { group('a static meta field with backwards compatible boilerplate', () { group('for a props class', () { test('has the wrong type', () { - setUpAndParse(factorySrc + propsSrc + componentSrc + ''' + setUpAndParse(factorySrc + propsSrc + component2Src + ''' class FooProps { static const StateMeta meta = _\$metaForFooProps; } @@ -734,7 +852,7 @@ main() { }); test('is initialized incorrectly', () { - setUpAndParse(factorySrc + propsSrc + componentSrc + ''' + setUpAndParse(factorySrc + propsSrc + component2Src + ''' class FooProps { static const PropsMeta meta = \$metaForBarProps; } @@ -744,7 +862,7 @@ main() { }); test('is private and initialized incorrectly', () { - setUpAndParse(factorySrc + privatePropsSrc + componentSrc + ''' + setUpAndParse(factorySrc + privatePropsSrc + component2Src + ''' class _FooProps { static const PropsMeta meta = \$metaForFooProps; } @@ -756,7 +874,7 @@ main() { group('for a state class', () { test('has the wrong type', () { - setUpAndParse(factorySrc + propsSrc + companionClassProps + stateSrc + componentSrc + ''' + setUpAndParse(factorySrc + propsSrc + companionClassProps + stateSrc + component2Src + ''' class FooState { static const PropsMeta meta = _\$metaForFooState; } @@ -765,7 +883,7 @@ main() { }); test('is initialized incorrectly', () { - setUpAndParse(factorySrc + propsSrc + companionClassProps + stateSrc + componentSrc + ''' + setUpAndParse(factorySrc + propsSrc + companionClassProps + stateSrc + component2Src + ''' class FooState { static const StateMeta meta = \$metaForBarState; } @@ -775,7 +893,7 @@ main() { }); test('is private and initialized incorrectly', () { - setUpAndParse(factorySrc + propsSrc + companionClassProps + componentSrc + privateStateSrc + ''' + setUpAndParse(factorySrc + propsSrc + companionClassProps + component2Src + privateStateSrc + ''' class _FooState { static const StateMeta meta = \$metaForBarState; } @@ -980,19 +1098,29 @@ main() { }); group('and throws an error when', () { - test('`subtypeOf` is an unsupported expression that is not an identifier', () { - expect(() { - setUpAndParse(''' - @Factory() - UiFactory Foo = _\$Foo; + group('`subtypeOf` is an unsupported expression that is not an identifier', () { + void sharedUnsupportedExpressionTest(String componentAnnotationName) { + expect(() { + setUpAndParse(''' + @Factory() + UiFactory Foo = _\$Foo; + + @Props() + class _\$FooProps {} + + @$componentAnnotationName(subtypeOf: const []) + class FooComponent {} + '''); + }, throwsA(startsWith('`subtypeOf` must be an identifier'))); + } - @Props() - class _\$FooProps {} + test('within a @Component() annotation (deprecated)', () { + sharedUnsupportedExpressionTest('Component'); + }); - @Component(subtypeOf: const []) - class FooComponent {} - '''); - }, throwsA(startsWith('`subtypeOf` must be an identifier'))); + test('within a @Component2() annotation', () { + sharedUnsupportedExpressionTest('Component2'); + }); }); }); }); diff --git a/test/vm_tests/builder/util.dart b/test/vm_tests/builder/util.dart index 20dd1dc95..5ad87485a 100644 --- a/test/vm_tests/builder/util.dart +++ b/test/vm_tests/builder/util.dart @@ -3,7 +3,9 @@ import 'package:mockito/mockito.dart'; const String factorySrc = '\n@Factory()\nUiFactory Foo = _\$Foo;\n'; const String componentSrc = '\n@Component()\nclass FooComponent {render() {return null;}}\n'; +const String component2Src = '\n@Component2()\nclass FooComponent {render() {return null;}}\n'; const String abstractComponentSrc = '\n@AbstractComponent()\nclass AbstractFooComponent {}\n'; +const String abstractComponent2Src = '\n@AbstractComponent2()\nclass AbstractFooComponent {}\n'; const String propsSrc = '\n@Props()\nclass _\$FooProps {}\n'; const String privatePropsSrc = '\n@Props()\nclass _\$_FooProps {}\n'; @@ -68,6 +70,7 @@ class OverReactSrc { this.componentBody: '', this.needsComponent: false, this.typeParameters: false, + this.componentVersion: 1, isPrivate: false}) : this.annotation = AnnotationType.abstractProps, @@ -101,6 +104,7 @@ class OverReactSrc { this.componentBody: '', this.needsComponent: false, this.typeParameters: false, + this.componentVersion: 1, isPrivate: false}) : this.annotation = AnnotationType.abstractState, @@ -129,6 +133,7 @@ class OverReactSrc { this.componentAnnotationArg: '', this.componentBody: '', this.typeParameters: false, + this.componentVersion: 1, isPrivate: false}) : this.annotation = AnnotationType.props, @@ -161,6 +166,7 @@ class OverReactSrc { this.componentBody: '', this.typeParameters: false, this.numMixins: 1, + this.componentVersion: 1, isPrivate: false}) : this.annotation = AnnotationType.propsMixin, @@ -190,6 +196,7 @@ class OverReactSrc { this.componentAnnotationArg: '', this.componentBody: '', this.typeParameters: false, + this.componentVersion: 1, isPrivate: false}) : this.annotation = AnnotationType.state, @@ -223,6 +230,7 @@ class OverReactSrc { this.componentBody: '', this.typeParameters: false, this.numMixins: 1, + this.componentVersion: 1, isPrivate: false}) : this.annotation = AnnotationType.stateMixin, @@ -237,6 +245,7 @@ class OverReactSrc { final String body; final String baseName; final bool needsComponent; + final int componentVersion; final int numMixins; final bool typeParameters; @@ -261,10 +270,11 @@ class OverReactSrc { final buffer = new StringBuffer(); if (needsComponent) { + String componentStr = componentVersion == 2 ? 'Component2' : 'Component'; if (!isAbstract(annotation)) { buffer.writeln('\n@Factory()\nUiFactory<$propsClassName> $baseName = _\$$baseName;\n'); } - buffer.writeln('@${isAbstract(annotation) ? 'Abstract' : ''}Component($componentAnnotationArg) ${_classKeyword(annotation)} $componentName {$componentBody}'); + buffer.writeln('@${isAbstract(annotation) ? 'Abstract' : ''}$componentStr($componentAnnotationArg) ${_classKeyword(annotation)} $componentName {$componentBody}'); // If we need a component, but we're not a props class, then we need to write a props class if (!isProps(this.annotation)) { diff --git a/web/component2/src/demo_components/button.dart b/web/component2/src/demo_components/button.dart index 7a917eabe..8232529d1 100644 --- a/web/component2/src/demo_components/button.dart +++ b/web/component2/src/demo_components/button.dart @@ -77,7 +77,7 @@ class _$ButtonProps extends UiProps { @State() class _$ButtonState extends UiState {} -@Component() +@Component2() class ButtonComponent extends UiStatefulComponent2 { @override Map getDefaultProps() => (newProps() diff --git a/web/component2/src/demo_components/button.over_react.g.dart b/web/component2/src/demo_components/button.over_react.g.dart index 5ae39b2fc..a089dd389 100644 --- a/web/component2/src/demo_components/button.over_react.g.dart +++ b/web/component2/src/demo_components/button.over_react.g.dart @@ -3,7 +3,7 @@ part of 'button.dart'; // ************************************************************************** -// OverReactGenerator +// OverReactBuilder (package:over_react/src/builder.dart) // ************************************************************************** // React component factory implementation. diff --git a/web/component2/src/demo_components/button_group.dart b/web/component2/src/demo_components/button_group.dart index dabb7c387..fefd19f83 100644 --- a/web/component2/src/demo_components/button_group.dart +++ b/web/component2/src/demo_components/button_group.dart @@ -32,7 +32,7 @@ class _$ButtonGroupProps extends UiProps { @State() class _$ButtonGroupState extends UiState {} -@Component() +@Component2() class ButtonGroupComponent extends UiStatefulComponent2 { @override diff --git a/web/component2/src/demo_components/button_group.over_react.g.dart b/web/component2/src/demo_components/button_group.over_react.g.dart index 8be42a9c7..22a8bee07 100644 --- a/web/component2/src/demo_components/button_group.over_react.g.dart +++ b/web/component2/src/demo_components/button_group.over_react.g.dart @@ -3,7 +3,7 @@ part of 'button_group.dart'; // ************************************************************************** -// OverReactGenerator +// OverReactBuilder (package:over_react/src/builder.dart) // ************************************************************************** // React component factory implementation. diff --git a/web/component2/src/demo_components/list_group.dart b/web/component2/src/demo_components/list_group.dart index ed8a40ab1..030198101 100644 --- a/web/component2/src/demo_components/list_group.dart +++ b/web/component2/src/demo_components/list_group.dart @@ -19,7 +19,7 @@ class _$ListGroupProps extends UiProps { ListGroupElementType elementType; } -@Component() +@Component2() class ListGroupComponent extends UiComponent2 { @override Map getDefaultProps() => (newProps() diff --git a/web/component2/src/demo_components/list_group.over_react.g.dart b/web/component2/src/demo_components/list_group.over_react.g.dart index 1df392cd3..6c035de49 100644 --- a/web/component2/src/demo_components/list_group.over_react.g.dart +++ b/web/component2/src/demo_components/list_group.over_react.g.dart @@ -3,7 +3,7 @@ part of 'list_group.dart'; // ************************************************************************** -// OverReactGenerator +// OverReactBuilder (package:over_react/src/builder.dart) // ************************************************************************** // React component factory implementation. diff --git a/web/component2/src/demo_components/list_group_item.dart b/web/component2/src/demo_components/list_group_item.dart index 3a93b7031..d3a7df415 100644 --- a/web/component2/src/demo_components/list_group_item.dart +++ b/web/component2/src/demo_components/list_group_item.dart @@ -83,7 +83,7 @@ class _$ListGroupItemProps extends UiProps { ButtonType type; } -@Component() +@Component2() class ListGroupItemComponent extends UiComponent2 { @override Map getDefaultProps() => (newProps() diff --git a/web/component2/src/demo_components/list_group_item.over_react.g.dart b/web/component2/src/demo_components/list_group_item.over_react.g.dart index 45c479d71..da5654445 100644 --- a/web/component2/src/demo_components/list_group_item.over_react.g.dart +++ b/web/component2/src/demo_components/list_group_item.over_react.g.dart @@ -3,7 +3,7 @@ part of 'list_group_item.dart'; // ************************************************************************** -// OverReactGenerator +// OverReactBuilder (package:over_react/src/builder.dart) // ************************************************************************** // React component factory implementation. diff --git a/web/component2/src/demo_components/progress.dart b/web/component2/src/demo_components/progress.dart index b708d1df8..f8d52f7bc 100644 --- a/web/component2/src/demo_components/progress.dart +++ b/web/component2/src/demo_components/progress.dart @@ -82,7 +82,7 @@ class _$ProgressState extends UiState { String id; } -@Component() +@Component2() class ProgressComponent extends UiStatefulComponent2 { @override Map getDefaultProps() => (newProps() diff --git a/web/component2/src/demo_components/progress.over_react.g.dart b/web/component2/src/demo_components/progress.over_react.g.dart index 4d416753e..430d4aa99 100644 --- a/web/component2/src/demo_components/progress.over_react.g.dart +++ b/web/component2/src/demo_components/progress.over_react.g.dart @@ -3,7 +3,7 @@ part of 'progress.dart'; // ************************************************************************** -// OverReactGenerator +// OverReactBuilder (package:over_react/src/builder.dart) // ************************************************************************** // React component factory implementation. diff --git a/web/component2/src/demo_components/tag.dart b/web/component2/src/demo_components/tag.dart index 5b6d5cb18..af5e490f0 100644 --- a/web/component2/src/demo_components/tag.dart +++ b/web/component2/src/demo_components/tag.dart @@ -26,7 +26,7 @@ class _$TagProps extends UiProps { bool isPill; } -@Component() +@Component2() class TagComponent extends UiComponent2 { @override Map getDefaultProps() => (newProps() diff --git a/web/component2/src/demo_components/tag.over_react.g.dart b/web/component2/src/demo_components/tag.over_react.g.dart index e3ad907c6..aa6439e7c 100644 --- a/web/component2/src/demo_components/tag.over_react.g.dart +++ b/web/component2/src/demo_components/tag.over_react.g.dart @@ -3,7 +3,7 @@ part of 'tag.dart'; // ************************************************************************** -// OverReactGenerator +// OverReactBuilder (package:over_react/src/builder.dart) // ************************************************************************** // React component factory implementation. diff --git a/web/component2/src/demo_components/toggle_button.dart b/web/component2/src/demo_components/toggle_button.dart index 2113252fa..f445dcf90 100644 --- a/web/component2/src/demo_components/toggle_button.dart +++ b/web/component2/src/demo_components/toggle_button.dart @@ -68,7 +68,7 @@ class _$ToggleButtonState extends ButtonState with bool isChecked; } -@Component(subtypeOf: ButtonComponent) +@Component2(subtypeOf: ButtonComponent) class ToggleButtonComponent extends ButtonComponent { // Refs diff --git a/web/component2/src/demo_components/toggle_button.over_react.g.dart b/web/component2/src/demo_components/toggle_button.over_react.g.dart index 38e715520..fc28f6e56 100644 --- a/web/component2/src/demo_components/toggle_button.over_react.g.dart +++ b/web/component2/src/demo_components/toggle_button.over_react.g.dart @@ -3,7 +3,7 @@ part of 'toggle_button.dart'; // ************************************************************************** -// OverReactGenerator +// OverReactBuilder (package:over_react/src/builder.dart) // ************************************************************************** // React component factory implementation. diff --git a/web/component2/src/demo_components/toggle_button_group.dart b/web/component2/src/demo_components/toggle_button_group.dart index 91d6b9afe..d72f2d4d3 100644 --- a/web/component2/src/demo_components/toggle_button_group.dart +++ b/web/component2/src/demo_components/toggle_button_group.dart @@ -32,7 +32,7 @@ class _$ToggleButtonGroupProps extends ButtonGroupProps with class _$ToggleButtonGroupState extends ButtonGroupState with AbstractInputStateMixin {} -@Component(subtypeOf: ButtonGroupComponent) +@Component2(subtypeOf: ButtonGroupComponent) class ToggleButtonGroupComponent extends ButtonGroupComponent { // Refs diff --git a/web/component2/src/demo_components/toggle_button_group.over_react.g.dart b/web/component2/src/demo_components/toggle_button_group.over_react.g.dart index a5bb05ccf..536ff8048 100644 --- a/web/component2/src/demo_components/toggle_button_group.over_react.g.dart +++ b/web/component2/src/demo_components/toggle_button_group.over_react.g.dart @@ -3,7 +3,7 @@ part of 'toggle_button_group.dart'; // ************************************************************************** -// OverReactGenerator +// OverReactBuilder (package:over_react/src/builder.dart) // ************************************************************************** // React component factory implementation. diff --git a/web/component2/src/shared/constants.over_react.g.dart b/web/component2/src/shared/constants.over_react.g.dart index b53c1421a..1fef35eb5 100644 --- a/web/component2/src/shared/constants.over_react.g.dart +++ b/web/component2/src/shared/constants.over_react.g.dart @@ -3,7 +3,7 @@ part of 'constants.dart'; // ************************************************************************** -// OverReactGenerator +// OverReactBuilder (package:over_react/src/builder.dart) // ************************************************************************** abstract class AbstractInputPropsMixin implements _$AbstractInputPropsMixin {