From 0152828360e241aabc494f5968c2c8922e81cdaa Mon Sep 17 00:00:00 2001 From: Andrew Buchan Date: Fri, 3 Apr 2026 15:35:37 +0100 Subject: [PATCH] Better directive specification --- .../babel-plugin-wallace/src/directives.ts | 78 ++++--- packages/babel-plugin-wallace/src/errors.ts | 32 +-- packages/babel-plugin-wallace/src/index.ts | 9 +- .../src/models/directive.ts | 203 +++++++++++++++--- .../babel-plugin-wallace/src/models/index.ts | 22 +- packages/wallace/test.babel.config.cjs | 7 +- .../07.directives.06.bind.spec.jsx | 42 ++-- .../07.directives.07.fixed.spec.jsx | 24 +-- .../tests/functionality/08.refs.spec.jsx | 4 +- .../tests/functionality/09.events.spec.jsx | 4 +- 10 files changed, 280 insertions(+), 145 deletions(-) diff --git a/packages/babel-plugin-wallace/src/directives.ts b/packages/babel-plugin-wallace/src/directives.ts index b5a1336..68c4b12 100644 --- a/packages/babel-plugin-wallace/src/directives.ts +++ b/packages/babel-plugin-wallace/src/directives.ts @@ -9,7 +9,14 @@ import * as t from "@babel/types"; import { wallaceConfig, FlagValue } from "./config"; import { ERROR_MESSAGES, error } from "./errors"; -import { Directive, TagNode, NodeValue, Qualifier } from "./models"; +import { + Directive, + TagNode, + NodeValue, + Qualifier, + ValueMode, + QualifierMode +} from "./models"; import { WATCH_CALLBACK_ARGS, SPECIAL_SYMBOLS, @@ -19,8 +26,8 @@ import { class ApplyDirective extends Directive { static attributeName = "apply"; - static allowNull = true; - static allowString = false; + static valueMode: ValueMode = ValueMode.ExpressionRequired; + static qualifierMode: QualifierMode = QualifierMode.NotAllowed; static mayAccessElement = true; apply(node: TagNode, value: NodeValue, _qualifier: Qualifier, _base: string) { node.addWatch(SPECIAL_SYMBOLS.noLookup, value.expression); @@ -29,7 +36,8 @@ class ApplyDirective extends Directive { class BindDirective extends Directive { static attributeName = "bind"; - static allowQualifier = true; + static valueMode: ValueMode = ValueMode.ExpressionRequired; + static qualifierMode: QualifierMode = QualifierMode.Optional; apply(node: TagNode, value: NodeValue, qualifier: Qualifier, _base: string) { node.setBindInstruction(value.expression, qualifier); } @@ -37,8 +45,8 @@ class BindDirective extends Directive { class ClassDirective extends Directive { static attributeName = "class"; - static allowString = true; - static allowQualifier = true; + static valueMode: ValueMode = ValueMode.EitherRequired; + static qualifierMode: QualifierMode = QualifierMode.Optional; apply(node: TagNode, value: NodeValue, qualifier: Qualifier, base: string) { if (qualifier) { node.addClassToggleTarget( @@ -58,6 +66,8 @@ class ClassDirective extends Directive { class CssDirective extends Directive { static attributeName = "css"; + static valueMode: ValueMode = ValueMode.ExpressionRequired; + static qualifierMode: QualifierMode = QualifierMode.NotAllowed; static mayAccessComponent = false; apply(node: TagNode, value: NodeValue, _qualifier: Qualifier, _base: string) { node.addStaticAttribute("class", value.expression); @@ -66,6 +76,8 @@ class CssDirective extends Directive { class CtrlDirective extends Directive { static attributeName = "ctrl"; + static valueMode: ValueMode = ValueMode.ExpressionRequired; + static qualifierMode: QualifierMode = QualifierMode.NotAllowed; static allowOnNested = true; static allowOnRepeated = true; static allowOnNormalElement = false; @@ -96,9 +108,8 @@ class ValueAsDateDirective extends Directive { class EventDirective extends Directive { static attributeName = "event"; - static allowExpression = false; - static allowNull = true; - static requireQualifier = true; + static valueMode: ValueMode = ValueMode.StringRequired; + static qualifierMode: QualifierMode = QualifierMode.SetsValue; apply(node: TagNode, value: NodeValue, qualifier: Qualifier, _base: string) { const eventName = qualifier || value.value; if (!DOM_EVENTS_LOWERCASE.includes(eventName)) { @@ -110,7 +121,8 @@ class EventDirective extends Directive { class FixedDirective extends Directive { static attributeName = "fixed"; - static requireQualifier = true; + static valueMode: ValueMode = ValueMode.ExpressionRequired; + static qualifierMode: QualifierMode = QualifierMode.Required; static mayAccessComponent = false; apply(node: TagNode, value: NodeValue, qualifier: Qualifier, _base: string) { node.addStaticAttribute(qualifier, value.expression); @@ -119,6 +131,8 @@ class FixedDirective extends Directive { class HideDirective extends Directive { static attributeName = "hide"; + static valueMode: ValueMode = ValueMode.ExpressionRequired; + static qualifierMode: QualifierMode = QualifierMode.NotAllowed; apply(node: TagNode, value: NodeValue, _qualifier: Qualifier, _base: string) { node.setVisibilityToggle(value.expression, true, false); } @@ -126,6 +140,8 @@ class HideDirective extends Directive { class HtmlDirective extends Directive { static attributeName = "html"; + static valueMode: ValueMode = ValueMode.ExpressionRequired; + static qualifierMode: QualifierMode = QualifierMode.NotAllowed; apply(node: TagNode, value: NodeValue, _qualifier: Qualifier, _base: string) { node.watchAttribute("innerHTML", value.expression); } @@ -133,6 +149,8 @@ class HtmlDirective extends Directive { class IfDirective extends Directive { static attributeName = "if"; + static valueMode: ValueMode = ValueMode.ExpressionRequired; + static qualifierMode: QualifierMode = QualifierMode.NotAllowed; static allowOnNested = true; apply(node: TagNode, value: NodeValue, _qualifier: Qualifier, _base: string) { node.setVisibilityToggle(value.expression, false, true); @@ -141,7 +159,8 @@ class IfDirective extends Directive { class KeyDirective extends Directive { static attributeName = "key"; - static allowString = true; + static valueMode: ValueMode = ValueMode.EitherRequired; + static qualifierMode: QualifierMode = QualifierMode.SetsValue; static allowOnRepeated = true; static allowOnNormalElement = false; apply(node: TagNode, value: NodeValue, _qualifier: Qualifier, _base: string) { @@ -149,9 +168,11 @@ class KeyDirective extends Directive { } } +// TODO: change to be on:click class OnEventDirective extends Directive { static attributeName = "on*"; - static allowString = true; + static valueMode: ValueMode = ValueMode.EitherRequired; + static qualifierMode: QualifierMode = QualifierMode.NotAllowed; static mayAccessElement = true; static mayAccessEvent = true; apply(node: TagNode, value: NodeValue, _qualifier: Qualifier, base: string) { @@ -165,19 +186,20 @@ class OnEventDirective extends Directive { class PartDirective extends Directive { static attributeName = "part"; + static valueMode: ValueMode = ValueMode.StringRequired; + static qualifierMode: QualifierMode = QualifierMode.SetsValue; static allowOnNested = true; static allowOnRepeated = true; - static allowNull = true; - static allowExpression = false; - static requireQualifier = true; - apply(node: TagNode, _value: NodeValue, qualifier: Qualifier, _base: string) { + apply(node: TagNode, value: NodeValue, qualifier: Qualifier, _base: string) { wallaceConfig.ensureFlagIstrue(node.path, FlagValue.allowParts); - node.setPart(qualifier); + node.setPart(qualifier || value.value); } } class PropsDirective extends Directive { static attributeName = "props"; + static valueMode: ValueMode = ValueMode.ExpressionRequired; + static qualifierMode: QualifierMode = QualifierMode.NotAllowed; static allowOnNested = true; static allowOnRepeated = true; static allowOnNormalElement = false; @@ -188,17 +210,18 @@ class PropsDirective extends Directive { class RefDirective extends Directive { static attributeName = "ref"; + static valueMode: ValueMode = ValueMode.StringRequired; + static qualifierMode: QualifierMode = QualifierMode.SetsValue; static allowOnNested = true; - static allowNull = true; - static allowExpression = false; - static requireQualifier = true; - apply(node: TagNode, _value: NodeValue, qualifier: Qualifier, _base: string) { - node.setRef(qualifier); + apply(node: TagNode, value: NodeValue, qualifier: Qualifier, _base: string) { + node.setRef(qualifier || value.value); } } class ShowDirective extends Directive { static attributeName = "show"; + static valueMode: ValueMode = ValueMode.ExpressionRequired; + static qualifierMode: QualifierMode = QualifierMode.NotAllowed; apply(node: TagNode, value: NodeValue, _qualifier: Qualifier, _base: string) { node.setVisibilityToggle(value.expression, false, false); } @@ -206,8 +229,8 @@ class ShowDirective extends Directive { class StyleDirective extends Directive { static attributeName = "style"; - static allowString = true; - static allowQualifier = true; + static valueMode: ValueMode = ValueMode.EitherRequired; + static qualifierMode: QualifierMode = QualifierMode.Optional; apply(node: TagNode, value: NodeValue, qualifier: Qualifier, base: string) { if (qualifier) { if (value.type === "string") { @@ -230,7 +253,8 @@ class StyleDirective extends Directive { class ToggleDirective extends Directive { static attributeName = "toggle"; - static requireQualifier = true; + static valueMode: ValueMode = ValueMode.ExpressionRequired; + static qualifierMode: QualifierMode = QualifierMode.Required; apply(node: TagNode, value: NodeValue, qualifier: Qualifier, _base: string) { if (!qualifier) { throw new Error("Toggle must have a qualifier"); @@ -241,8 +265,8 @@ class ToggleDirective extends Directive { class UniqueDirective extends Directive { static attributeName = "unique"; - static allowExpression = false; - static allowNull = true; + static valueMode: ValueMode = ValueMode.NotAllowed; + static qualifierMode: QualifierMode = QualifierMode.NotAllowed; apply(node: TagNode, _value: NodeValue, _qualifier: Qualifier, _base: string) { node.component.unique = true; } diff --git a/packages/babel-plugin-wallace/src/errors.ts b/packages/babel-plugin-wallace/src/errors.ts index 794f57e..94392c2 100644 --- a/packages/babel-plugin-wallace/src/errors.ts +++ b/packages/babel-plugin-wallace/src/errors.ts @@ -9,25 +9,38 @@ export const ERROR_MESSAGES = { CLASS_METHOD_MUST_BE_PROPERTY_JSX: "Function returning JSX in a class must be assigned to property 'jsx'", CAPITALISED_COMPONENT_NAME: "Component name must be capitalized.", + // TODO: remove CANNOT_USE_IF_ON_ROOT_ELEMENT: "Cannot use `if` directive on root element.", - CANNOT_USE_DIRECTIVE_ON_NESTED_ELEMENT: (directive: string) => { + DIRECTIVE_NOT_ALLOWED_ON_NESTED_ELEMENT: (directive: string) => { return `The \`${directive}\` directive may not be used on nested elements.`; }, - CANNOT_USE_DIRECTIVE_ON_REPEATED_ELEMENT: (directive: string) => { + DIRECTIVE_NOT_ALLOWED_ON_REPEATED_ELEMENT: (directive: string) => { return `The \`${directive}\` directive may not be used on repeated elements.`; }, - CANNOT_USE_DIRECTIVE_WITHOUT_QUALIFIER: (directive: string) => { + DIRECTIVE_ALREADY_DEFINED: (directive: string) => { + return `The \`${directive}\` directive has already been defined on this node.`; + }, + DIRECTIVE_REQUIRES_QUALIFIER: (directive: string) => { return `The \`${directive}\` directive must have a qualifier.`; }, - CANNOT_USE_DIRECTIVE_WITH_QUALIFIER: (directive: string) => { + DIRECTIVE_DISALLOWS_QUALIFIER: (directive: string) => { return `The \`${directive}\` directive may not have a qualifier.`; }, - DIRECTIVE_ALREADY_DEFINED: (directive: string) => { - return `The \`${directive}\` directive has already been defined on this node.`; - }, DIRECTIVE_MAY_NOT_ACCESS_SCOPE_VAR: (directive: string, name: string) => { return `The \`${directive}\` directive may not access scoped variable \`${name}\`.`; }, + DIRECTIVE_REQUIRES_QUALIFIER_OR_VALUE: (directive: string) => { + return `The \`${directive}\` directive requires a qualifier or value, not both.`; + }, + DIRECTIVE_EITHER_VALUE_REQUIRED: (directive: string) => { + return `The \`${directive}\` directive requires a value.`; + }, + DIRECTIVE_VALUE_REQUIRED: (type: string, directive: string) => { + return `The \`${directive}\` directive requires a value of type \`${type}\`.`; + }, + DIRECTIVE_NO_VALUE_ALLOWED: (directive: string) => { + return `The \`${directive}\` directive does not allow a value.`; + }, EVENT_USED_WITHOUT_BIND: "The `event` directive must be used with the `bind` directive.", FLAG_REQUIRED: (flag: string) => { @@ -43,11 +56,6 @@ export const ERROR_MESSAGES = { INVALID_EVENT_NAME: (event: string) => `\`${event}\` is not a valid event. Must be lowercase without \`on\` prefix. E.g. \`event:keyup\`.`, NESTED_COMPONENT_MUST_BE_CAPTIALIZED: "Nested component must be capitalized.", - DIRECTIVE_INVALID_TYPE: (directive: string, allowed: string[], actual: string) => { - return allowed.length - ? `The \`${directive}\` directive value must be of type ${allowed.join(" or ")}. Found: ${actual}.` - : `The \`${directive}\` directive value must be of type ${allowed[0]}. Found: ${actual}.`; - }, PLACEHOLDER_MAY_NOT_BE_LITERAL_OBJECT: "Literal objects in placeholders not allowed as they will become constants.", JSX_ELEMENTS_NOT_ALLOWED_IN_EXPRESSIONS: "JSX elements are not allowed in expressions.", diff --git a/packages/babel-plugin-wallace/src/index.ts b/packages/babel-plugin-wallace/src/index.ts index 3a40766..61194e0 100644 --- a/packages/babel-plugin-wallace/src/index.ts +++ b/packages/babel-plugin-wallace/src/index.ts @@ -1,12 +1,9 @@ import "source-map-support/register.js"; // Ensures correct line numbers in stack traces. import * as constants from "./constants"; -import { wallaceConfig } from "./config"; -import { Directive, NodeValue } from "./models"; +export { wallaceConfig } from "./config"; +export { Directive, NodeValue, QualifierMode, ValueMode } from "./models"; import { wallacePlugin } from "./visitors/main"; export default wallacePlugin; -/** - * These exports are for custom plugin development. - */ -export { wallaceConfig, Directive, NodeValue, constants }; +export { constants }; diff --git a/packages/babel-plugin-wallace/src/models/directive.ts b/packages/babel-plugin-wallace/src/models/directive.ts index b2cc474..fed1d2c 100644 --- a/packages/babel-plugin-wallace/src/models/directive.ts +++ b/packages/babel-plugin-wallace/src/models/directive.ts @@ -14,21 +14,47 @@ export interface NodeValue { export type Qualifier = string | undefined; +export enum QualifierMode { + Required, + Optional, + SetsValue, // sets value as string if supplied + NotAllowed +} + +export enum ValueMode { + StringRequired, + StringOptional, + ExpressionRequired, + ExpressionOptional, + EitherRequired, + EitherOptional, + NotAllowed +} + +const ValidModesForSetsValue = [ + ValueMode.StringRequired, + ValueMode.StringOptional, + ValueMode.EitherRequired, + ValueMode.EitherOptional +]; + export class Directive { static attributeName: string; static help: string; - static allowExpression = true; - static allowNull = false; - static allowString = false; - static allowQualifier = false; - static requireQualifier = false; + // static allowExpression = true; + // static allowNull = false; + // static allowString = false; + // static allowQualifier = false; + // static requireQualifier = false; + static valueMode: ValueMode = ValueMode.ExpressionRequired; + static qualifierMode: QualifierMode = QualifierMode.NotAllowed; static allowOnNested = false; static allowOnRepeated = false; static allowOnNormalElement = true; static mayAccessComponent = true; static mayAccessElement = false; static mayAccessEvent = false; - static allowedTypes: { [key: string]: NodeValue["type"] }; + // static allowedTypes: { [key: string]: NodeValue["type"] }; apply(node: TagNode, value: NodeValue, qualifier: Qualifier, base: string) {} validate( node: TagNode, @@ -38,55 +64,152 @@ export class Directive { component: Component ) { const constructor = this.constructor as typeof Directive; - this.validateType(node, value, constructor); + this.validateTypeAndQualifier(node, value, qualifier, constructor); this.validateNestedAndRepeat(node, constructor); - this.validateQualifier(node, qualifier, constructor); this.validateScopeVariablAccess(node, value, constructor, component); } - validateType(node: TagNode, value: NodeValue, constructor: typeof Directive) { - const { attributeName, allowExpression, allowString, allowNull } = constructor; - const allowedTypes = [ - allowExpression && "expression", - allowString && "string", - allowNull && "null" - ].filter(Boolean); - if (!allowedTypes.includes(value.type)) { + validateTypeAndQualifier( + node: TagNode, + value: NodeValue, + qualifier: Qualifier, + constructor: typeof Directive + ) { + /* + check if SetsValue, then remaining qualifier checks. + + */ + const { qualifierMode, valueMode } = constructor; + + // Validate special case for SetsValue. + if (qualifierMode === QualifierMode.SetsValue && qualifier) { + ensureValidValueModeForSetsValue(valueMode, constructor); + if (value.expression || value.value) { + error( + node.path, + ERROR_MESSAGES.DIRECTIVE_REQUIRES_QUALIFIER_OR_VALUE(constructor.attributeName) + ); + } + value.value = qualifier; + } + + // Standard qualifer validation. + if (qualifierMode === QualifierMode.Required && !qualifier) { error( node.path, - ERROR_MESSAGES.DIRECTIVE_INVALID_TYPE(attributeName, allowedTypes, value.type) + ERROR_MESSAGES.DIRECTIVE_REQUIRES_QUALIFIER(constructor.attributeName) ); } + if (qualifierMode === QualifierMode.NotAllowed && qualifier) { + error( + node.path, + ERROR_MESSAGES.DIRECTIVE_DISALLOWS_QUALIFIER(constructor.attributeName) + ); + } + + if (value.value && value.expression) { + throw new Error("Internal error: directive got both a value and expression"); + } + + // Validate value + switch (valueMode) { + case ValueMode.StringRequired: + if (!value.value) + error( + node.path, + ERROR_MESSAGES.DIRECTIVE_VALUE_REQUIRED("string", constructor.attributeName) + ); + break; + case ValueMode.StringOptional: + if (value.expression) + error( + node.path, + ERROR_MESSAGES.DIRECTIVE_VALUE_REQUIRED("string", constructor.attributeName) + ); + break; + case ValueMode.ExpressionRequired: + if (!value.expression) + error( + node.path, + ERROR_MESSAGES.DIRECTIVE_VALUE_REQUIRED( + "expression", + constructor.attributeName + ) + ); + break; + case ValueMode.ExpressionOptional: + if (value.value) + error( + node.path, + ERROR_MESSAGES.DIRECTIVE_VALUE_REQUIRED( + "expression", + constructor.attributeName + ) + ); + break; + case ValueMode.EitherRequired: + if (!(value.expression || value.value)) + error( + node.path, + ERROR_MESSAGES.DIRECTIVE_EITHER_VALUE_REQUIRED(constructor.attributeName) + ); + break; + case ValueMode.EitherOptional: + // Do nothing + break; + case ValueMode.NotAllowed: + if (value.expression || value.value) { + error( + node.path, + ERROR_MESSAGES.DIRECTIVE_NO_VALUE_ALLOWED(constructor.attributeName) + ); + } + break; + } } + // validateType(node: TagNode, value: NodeValue, constructor: typeof Directive) { + // const { attributeName, allowExpression, allowString, allowNull } = constructor; + // const allowedTypes = [ + // allowExpression && "expression", + // allowString && "string", + // allowNull && "null" + // ].filter(Boolean); + // if (!allowedTypes.includes(value.type)) { + // error( + // node.path, + // ERROR_MESSAGES.DIRECTIVE_INVALID_TYPE(attributeName, allowedTypes, value.type) + // ); + // } + // } validateNestedAndRepeat(node: TagNode, constructor: typeof Directive) { const { attributeName, allowOnRepeated, allowOnNested } = constructor; if (!allowOnRepeated && node.isRepeatedComponent) { error( node.path, - ERROR_MESSAGES.CANNOT_USE_DIRECTIVE_ON_REPEATED_ELEMENT(attributeName) + ERROR_MESSAGES.DIRECTIVE_NOT_ALLOWED_ON_REPEATED_ELEMENT(attributeName) ); } if (!allowOnNested && node.isNestedComponent) { error( node.path, - ERROR_MESSAGES.CANNOT_USE_DIRECTIVE_ON_NESTED_ELEMENT(attributeName) - ); - } - } - validateQualifier(node: TagNode, qualifier: Qualifier, constructor: typeof Directive) { - let { attributeName, allowQualifier, requireQualifier } = constructor; - if (requireQualifier) { - allowQualifier = true; - } - if (requireQualifier && !qualifier) { - error( - node.path, - ERROR_MESSAGES.CANNOT_USE_DIRECTIVE_WITHOUT_QUALIFIER(attributeName) + ERROR_MESSAGES.DIRECTIVE_NOT_ALLOWED_ON_NESTED_ELEMENT(attributeName) ); } - if (!allowQualifier && qualifier) { - error(node.path, ERROR_MESSAGES.CANNOT_USE_DIRECTIVE_WITH_QUALIFIER(attributeName)); - } } + // validateQualifier(node: TagNode, qualifier: Qualifier, constructor: typeof Directive) { + // let { attributeName, allowQualifier, requireQualifier } = constructor; + // if (requireQualifier) { + // allowQualifier = true; + // } + // if (requireQualifier && !qualifier) { + // error( + // node.path, + // ERROR_MESSAGES.CANNOT_USE_DIRECTIVE_WITHOUT_QUALIFIER(attributeName) + // ); + // } + // if (!allowQualifier && qualifier) { + // error(node.path, ERROR_MESSAGES.CANNOT_USE_DIRECTIVE_WITH_QUALIFIER(attributeName)); + // } + // } /* Ensures the expression only accesses the scope variables it is allowed to. @@ -178,3 +301,15 @@ function getReferencedScopedVariables(path: NodePath, component: Component): str }); return Array.from(refs); } + +function ensureValidValueModeForSetsValue( + valueMode: ValueMode, + constructor: typeof Directive +) { + if (!ValidModesForSetsValue.includes(valueMode)) { + throw new Error( + `Invalid directive configuration for ${constructor.attributeName}. +When qualifierMode is 'SetsValue' then valueMode must be one of: ${ValidModesForSetsValue}.` + ); + } +} diff --git a/packages/babel-plugin-wallace/src/models/index.ts b/packages/babel-plugin-wallace/src/models/index.ts index ab0289f..fefbc26 100644 --- a/packages/babel-plugin-wallace/src/models/index.ts +++ b/packages/babel-plugin-wallace/src/models/index.ts @@ -1,24 +1,10 @@ -import { Module } from "./module"; -import { Directive, NodeValue, Qualifier } from "./directive"; -import { Component, WalkTracker } from "./component"; -import { +export { Module } from "./module"; +export { Directive, NodeValue, Qualifier, ValueMode, QualifierMode } from "./directive"; +export { Component, WalkTracker } from "./component"; +export { ExtractedNode, TagNode, DynamicTextNode, PlainTextNode, VisibilityToggle } from "./node"; - -export { - Component, - VisibilityToggle, - Directive, - NodeValue, - ExtractedNode, - TagNode, - DynamicTextNode, - PlainTextNode, - Module, - Qualifier, - WalkTracker -}; diff --git a/packages/wallace/test.babel.config.cjs b/packages/wallace/test.babel.config.cjs index 5aebc53..b5bd079 100644 --- a/packages/wallace/test.babel.config.cjs +++ b/packages/wallace/test.babel.config.cjs @@ -1,4 +1,4 @@ -const { Directive } = require("babel-plugin-wallace"); +const { Directive, QualifierMode, ValueMode } = require("babel-plugin-wallace"); const { flags } = require("./flags.cjs"); /** @@ -9,9 +9,8 @@ const { flags } = require("./flags.cjs"); */ class TestDirectiveInConfig extends Directive { static attributeName = "test-directive"; - static allowNull = true; - static allowString = true; - static allowQualifier = true; + static qualifierMode = QualifierMode.Optional; + static valueMode = ValueMode.EitherOptional; apply(node, value, qualifier, base) { node.addFixedAttribute("value-value", value.value); node.addFixedAttribute("value-expression", value.expression); diff --git a/packages/wallace/tests/functionality/07.directives.06.bind.spec.jsx b/packages/wallace/tests/functionality/07.directives.06.bind.spec.jsx index cb7b685..298c3f3 100644 --- a/packages/wallace/tests/functionality/07.directives.06.bind.spec.jsx +++ b/packages/wallace/tests/functionality/07.directives.06.bind.spec.jsx @@ -23,7 +23,7 @@ describe("Bind directive", () => { ) `; expect(code).toCompileWithError( - "The `bind` directive value must be of type expression. Found: null." + "The `bind` directive requires a value of type `expression`." ); }); @@ -31,23 +31,14 @@ describe("Bind directive", () => { const code = ` const Bar = () => ( - ) - `; + ) + `; expect(code).toCompileWithError( - "The `bind` directive value must be of type expression. Found: string." + "The `bind` directive requires a value of type `expression`." ); }); - test("allows raw", () => { - const code = ` - const Bar = (user) => ( - - ) - `; - expect(code).toCompileWithoutError(); - }); - - test("allows alternative property", () => { + test("allows qualifier", () => { const code = ` const Bar = (user) => ( @@ -69,15 +60,14 @@ describe("Event specification", () => { ); }); - // TODO: add more once we have fixed validation - test("disallows expression", () => { + test("disallows value and qualifier", () => { const code = ` const Foo = (user) => ( - + ) `; expect(code).toCompileWithError( - "The `event` directive value must be of type null. Found: expression." + "The `event` directive requires a qualifier or value, not both." ); }); @@ -104,14 +94,14 @@ describe("Event specification", () => { expect(code).toCompileWithoutError(); }); - // test("allows valid event name as string", () => { - // const code = ` - // const Bar = (user) => ( - // - // ) - // `; - // expect(code).toCompileWithoutError(); - // }); + test("allows valid event name as string", () => { + const code = ` + const Bar = (user) => ( + + ) + `; + expect(code).toCompileWithoutError(); + }); }); test("Change event updates data", () => { diff --git a/packages/wallace/tests/functionality/07.directives.07.fixed.spec.jsx b/packages/wallace/tests/functionality/07.directives.07.fixed.spec.jsx index d0379bb..d5aa320 100644 --- a/packages/wallace/tests/functionality/07.directives.07.fixed.spec.jsx +++ b/packages/wallace/tests/functionality/07.directives.07.fixed.spec.jsx @@ -1,44 +1,42 @@ import { testMount } from "../utils"; describe("Fixed specification", () => { - test("disallows null", () => { + test("disallows no value", () => { const code = ` const Bar = () => (
) `; - expect(code).toCompileWithError( - "The \`fixed\` directive value must be of type expression. Found: null." - ); + expect(code).toCompileWithError("The `fixed` directive must have a qualifier."); }); test("disallows string", () => { const code = ` const Bar = () => ( -
+
) `; expect(code).toCompileWithError( - "The \`fixed\` directive value must be of type expression. Found: string." + "The `fixed` directive requires a value of type `expression`." ); }); - test("disallows expression without qualifier", () => { + test("allows expression with qualifier", () => { const code = ` const Foo = () => ( -
+
) `; - expect(code).toCompileWithError("The \`fixed\` directive must have a qualifier."); + expect(code).toCompileWithoutError(); }); - test("allows expression with qualifier", () => { + test("disallows expression without qualifier", () => { const code = ` const Foo = () => ( -
+
) `; - expect(code).toCompileWithoutError(); + expect(code).toCompileWithError("The `fixed` directive must have a qualifier."); }); test("disallows access to props", () => { @@ -48,7 +46,7 @@ describe("Fixed specification", () => { ) `; expect(code).toCompileWithError( - "The \`fixed\` directive may not access scoped variable `props`." + "The `fixed` directive may not access scoped variable `props`." ); }); diff --git a/packages/wallace/tests/functionality/08.refs.spec.jsx b/packages/wallace/tests/functionality/08.refs.spec.jsx index e6edba9..1915f73 100644 --- a/packages/wallace/tests/functionality/08.refs.spec.jsx +++ b/packages/wallace/tests/functionality/08.refs.spec.jsx @@ -1,7 +1,7 @@ import { testMount } from "../utils"; describe("Specification", () => { - test("refs doesn' allow expression", () => { + test("disallows expression", () => { const src = ` const A = () => (
@@ -10,7 +10,7 @@ describe("Specification", () => { ); `; expect(src).toCompileWithError( - "The `ref` directive value must be of type null. Found: expression." + "The `ref` directive requires a value of type `string`." ); }); diff --git a/packages/wallace/tests/functionality/09.events.spec.jsx b/packages/wallace/tests/functionality/09.events.spec.jsx index b228dd4..609b7fb 100644 --- a/packages/wallace/tests/functionality/09.events.spec.jsx +++ b/packages/wallace/tests/functionality/09.events.spec.jsx @@ -7,9 +7,7 @@ describe("Event directive", () => {