[fix] adjust style directive AST (#7127)

- deduplicate type name: Is now "StyleDirective"
- Don't transform value array to template literal in the AST phase but in the compiler phase. This ensures other tools know what the raw output was and that start/end positions are available
pull/7135/head
Simon H 4 years ago committed by GitHub
parent 4ae20d7fdf
commit 7044a32a2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -7,7 +7,7 @@ import Transition from './Transition';
import Animation from './Animation'; import Animation from './Animation';
import Action from './Action'; import Action from './Action';
import Class from './Class'; import Class from './Class';
import Style from './Style'; import StyleDirective from './StyleDirective';
import Text from './Text'; import Text from './Text';
import { namespaces } from '../../utils/namespaces'; import { namespaces } from '../../utils/namespaces';
import map_children from './shared/map_children'; import map_children from './shared/map_children';
@ -181,7 +181,7 @@ export default class Element extends Node {
actions: Action[] = []; actions: Action[] = [];
bindings: Binding[] = []; bindings: Binding[] = [];
classes: Class[] = []; classes: Class[] = [];
styles: Style[] = []; styles: StyleDirective[] = [];
handlers: EventHandler[] = []; handlers: EventHandler[] = [];
lets: Let[] = []; lets: Let[] = [];
intro?: Transition = null; intro?: Transition = null;
@ -265,8 +265,8 @@ export default class Element extends Node {
this.classes.push(new Class(component, this, scope, node)); this.classes.push(new Class(component, this, scope, node));
break; break;
case 'Style': case 'StyleDirective':
this.styles.push(new Style(component, this, scope, node)); this.styles.push(new StyleDirective(component, this, scope, node));
break; break;
case 'EventHandler': case 'EventHandler':

@ -1,22 +0,0 @@
import Node from './shared/Node';
import Expression from './shared/Expression';
import { TemplateNode } from '../../interfaces';
import TemplateScope from './shared/TemplateScope';
import Component from '../Component';
export default class Style extends Node {
type: 'Style';
name: string;
expression: Expression;
should_cache: boolean;
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info);
this.name = info.name;
this.expression = new Expression(component, this, scope, info.expression);
this.should_cache = info.expression.type === 'TemplateLiteral' && info.expression.expressions.length > 0;
}
}

@ -0,0 +1,39 @@
import { TemplateNode } from '../../interfaces';
import Component from '../Component';
import { nodes_to_template_literal } from '../utils/nodes_to_template_literal';
import Expression from './shared/Expression';
import Node from './shared/Node';
import TemplateScope from './shared/TemplateScope';
export default class StyleDirective extends Node {
type: 'StyleDirective';
name: string;
expression: Expression;
should_cache: boolean;
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info);
this.name = info.name;
// Convert the value array to an expression so it's easier to handle
// the StyleDirective going forward.
if (info.value === true || (info.value.length === 1 && info.value[0].type === 'MustacheTag')) {
const identifier = info.value === true
? {
type: 'Identifier',
start: info.end - info.name.length,
end: info.end,
name: info.name
} as any
: info.value[0].expression;
this.expression = new Expression(component, this, scope, identifier);
this.should_cache = false;
} else {
const raw_expression = nodes_to_template_literal(info.value);
this.expression = new Expression(component, this, scope, raw_expression);
this.should_cache = raw_expression.expressions.length > 0;
}
}
}

@ -8,7 +8,7 @@ import Binding from './Binding';
import Body from './Body'; import Body from './Body';
import CatchBlock from './CatchBlock'; import CatchBlock from './CatchBlock';
import Class from './Class'; import Class from './Class';
import Style from './Style'; import StyleDirective from './StyleDirective';
import Comment from './Comment'; import Comment from './Comment';
import ConstTag from './ConstTag'; import ConstTag from './ConstTag';
import DebugTag from './DebugTag'; import DebugTag from './DebugTag';
@ -63,7 +63,7 @@ export type INode = Action
| RawMustacheTag | RawMustacheTag
| Slot | Slot
| SlotTemplate | SlotTemplate
| Style | StyleDirective
| Tag | Tag
| Text | Text
| ThenBlock | ThenBlock

@ -0,0 +1,37 @@
import { TemplateElement, TemplateLiteral } from 'estree';
import { MustacheTag, Text } from '../../interfaces';
/**
* Transforms a list of Text and MustacheTags into a TemplateLiteral expression.
* Start/End positions on the elements of the expression are not set.
*/
export function nodes_to_template_literal(value: Array<Text | MustacheTag>): TemplateLiteral {
const literal: TemplateLiteral = {
type: 'TemplateLiteral',
expressions: [],
quasis: []
};
let quasi: TemplateElement = {
type: 'TemplateElement',
value: { raw: '', cooked: null },
tail: false
};
value.forEach((node) => {
if (node.type === 'Text') {
quasi.value.raw += node.raw;
} else if (node.type === 'MustacheTag') {
literal.quasis.push(quasi);
literal.expressions.push(node.expression as any);
quasi = {
type: 'TemplateElement',
value: { raw: '', cooked: null },
tail: false
};
}
});
quasi.tail = true;
literal.quasis.push(quasi);
return literal;
}

@ -44,13 +44,18 @@ export type DirectiveType = 'Action'
| 'Animation' | 'Animation'
| 'Binding' | 'Binding'
| 'Class' | 'Class'
| 'Style' | 'StyleDirective'
| 'EventHandler' | 'EventHandler'
| 'Let' | 'Let'
| 'Ref' | 'Ref'
| 'Transition'; | 'Transition';
interface BaseDirective extends BaseNode { interface BaseDirective extends BaseNode {
type: DirectiveType;
name: string;
}
interface BaseExpressionDirective extends BaseDirective {
type: DirectiveType; type: DirectiveType;
expression: null | Node; expression: null | Node;
name: string; name: string;
@ -74,13 +79,13 @@ export interface SpreadAttribute extends BaseNode {
expression: Node; expression: Node;
} }
export interface Transition extends BaseDirective { export interface Transition extends BaseExpressionDirective {
type: 'Transition'; type: 'Transition';
intro: boolean; intro: boolean;
outro: boolean; outro: boolean;
} }
export type Directive = BaseDirective | Transition; export type Directive = BaseDirective | BaseExpressionDirective | Transition;
export type TemplateNode = Text export type TemplateNode = Text
| ConstTag | ConstTag

@ -1,14 +1,13 @@
import { TemplateLiteral, TemplateElement, Expression } from 'estree'; import { Directive, DirectiveType, TemplateNode, Text } from '../../interfaces';
import { extract_svelte_ignore } from '../../utils/extract_svelte_ignore';
import fuzzymatch from '../../utils/fuzzymatch';
import { is_void } from '../../utils/names';
import parser_errors from '../errors';
import { Parser } from '../index';
import read_expression from '../read/expression'; import read_expression from '../read/expression';
import read_script from '../read/script'; import read_script from '../read/script';
import read_style from '../read/style'; import read_style from '../read/style';
import { decode_character_references, closing_tag_omitted } from '../utils/html'; import { closing_tag_omitted, decode_character_references } from '../utils/html';
import { is_void } from '../../utils/names';
import { Parser } from '../index';
import { Directive, DirectiveType, TemplateNode, Text, MustacheTag } from '../../interfaces';
import fuzzymatch from '../../utils/fuzzymatch';
import { extract_svelte_ignore } from '../../utils/extract_svelte_ignore';
import parser_errors from '../errors';
// eslint-disable-next-line no-useless-escape // eslint-disable-next-line no-useless-escape
const valid_tag_name = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/; const valid_tag_name = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/;
@ -271,36 +270,6 @@ function read_tag_name(parser: Parser) {
return name; return name;
} }
function node_to_template_literal(value: Array<Text | MustacheTag>): TemplateLiteral {
let quasi: TemplateElement = {
type: 'TemplateElement',
value: { raw: '', cooked: null },
tail: false
};
const literal: TemplateLiteral = {
type: 'TemplateLiteral',
expressions: [],
quasis: []
};
value.forEach((node) => {
if (node.type === 'Text') {
quasi.value.raw += node.raw;
} else if (node.type === 'MustacheTag') {
literal.quasis.push(quasi);
literal.expressions.push(node.expression as Expression);
quasi = {
type: 'TemplateElement',
value: { raw: '', cooked: null },
tail: false
};
}
});
quasi.tail = true;
literal.quasis.push(quasi);
return literal;
}
function read_attribute(parser: Parser, unique_names: Set<string>) { function read_attribute(parser: Parser, unique_names: Set<string>) {
const start = parser.index; const start = parser.index;
@ -396,14 +365,22 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
parser.error(parser_errors.invalid_ref_directive(directive_name), start); parser.error(parser_errors.invalid_ref_directive(directive_name), start);
} }
if (type === 'StyleDirective') {
return {
start,
end,
type,
name: directive_name,
value
};
}
const first_value = value[0]; const first_value = value[0];
let expression = null; let expression = null;
if (first_value) { if (first_value) {
const attribute_contains_text = (value as any[]).length > 1 || first_value.type === 'Text'; const attribute_contains_text = (value as any[]).length > 1 || first_value.type === 'Text';
if (type === 'Style') { if (attribute_contains_text) {
expression = attribute_contains_text ? node_to_template_literal(value as Array<Text | MustacheTag>) : first_value.expression;
} else if (attribute_contains_text) {
parser.error(parser_errors.invalid_directive_value, first_value.start); parser.error(parser_errors.invalid_directive_value, first_value.start);
} else { } else {
expression = first_value.expression; expression = first_value.expression;
@ -426,7 +403,7 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
} }
// Directive name is expression, e.g. <p class:isRed /> // Directive name is expression, e.g. <p class:isRed />
if (!directive.expression && (type === 'Binding' || type === 'Class' || type === 'Style')) { if (!directive.expression && (type === 'Binding' || type === 'Class')) {
directive.expression = { directive.expression = {
start: directive.start + colon_index + 1, start: directive.start + colon_index + 1,
end: directive.end, end: directive.end,
@ -454,7 +431,7 @@ function get_directive_type(name: string): DirectiveType {
if (name === 'animate') return 'Animation'; if (name === 'animate') return 'Animation';
if (name === 'bind') return 'Binding'; if (name === 'bind') return 'Binding';
if (name === 'class') return 'Class'; if (name === 'class') return 'Class';
if (name === 'style') return 'Style'; if (name === 'style') return 'StyleDirective';
if (name === 'on') return 'EventHandler'; if (name === 'on') return 'EventHandler';
if (name === 'let') return 'Let'; if (name === 'let') return 'Let';
if (name === 'ref') return 'Ref'; if (name === 'ref') return 'Ref';

@ -13,19 +13,13 @@
{ {
"start": 5, "start": 5,
"end": 16, "end": 16,
"type": "Style", "type": "StyleDirective",
"name": "color", "name": "color",
"modifiers": [], "value": true
"expression": {
"start": 11,
"end": 16,
"name": "color",
"type": "Identifier"
}
} }
], ],
"children": [] "children": []
} }
] ]
} }
} }

@ -1 +1,7 @@
<div style:color="red"></div> <div style:color="red"></div>
<div style:color='red'></div>
<div style:color=red></div>
<div style:color="red{variable}"></div>
<div style:color='red{variable}'></div>
<div style:color=red{variable}></div>
<div style:color={`template${literal}`}></div>

@ -1,7 +1,7 @@
{ {
"html": { "html": {
"start": 0, "start": 0,
"end": 29, "end": 252,
"type": "Fragment", "type": "Fragment",
"children": [ "children": [
{ {
@ -13,23 +13,339 @@
{ {
"start": 5, "start": 5,
"end": 22, "end": 22,
"type": "Style", "type": "StyleDirective",
"name": "color", "name": "color",
"modifiers": [], "value": [
"expression": { {
"type": "TemplateLiteral", "start": 18,
"expressions": [], "end": 21,
"quasis": [ "type": "Text",
{ "raw": "red",
"type": "TemplateElement", "data": "red"
"value": { }
"raw": "red", ]
"cooked": null }
],
"children": []
},
{
"start": 29,
"end": 30,
"type": "Text",
"raw": "\n",
"data": "\n"
},
{
"start": 30,
"end": 59,
"type": "Element",
"name": "div",
"attributes": [
{
"start": 35,
"end": 52,
"type": "StyleDirective",
"name": "color",
"value": [
{
"start": 48,
"end": 51,
"type": "Text",
"raw": "red",
"data": "red"
}
]
}
],
"children": []
},
{
"start": 59,
"end": 60,
"type": "Text",
"raw": "\n",
"data": "\n"
},
{
"start": 60,
"end": 87,
"type": "Element",
"name": "div",
"attributes": [
{
"start": 65,
"end": 80,
"type": "StyleDirective",
"name": "color",
"value": [
{
"start": 77,
"end": 80,
"type": "Text",
"raw": "red",
"data": "red"
}
]
}
],
"children": []
},
{
"start": 87,
"end": 88,
"type": "Text",
"raw": "\n",
"data": "\n"
},
{
"start": 88,
"end": 127,
"type": "Element",
"name": "div",
"attributes": [
{
"start": 93,
"end": 120,
"type": "StyleDirective",
"name": "color",
"value": [
{
"start": 106,
"end": 109,
"type": "Text",
"raw": "red",
"data": "red"
},
{
"start": 109,
"end": 119,
"type": "MustacheTag",
"expression": {
"type": "Identifier",
"start": 110,
"end": 118,
"loc": {
"start": {
"line": 4,
"column": 22
},
"end": {
"line": 4,
"column": 30
}
},
"name": "variable"
}
}
]
}
],
"children": []
},
{
"start": 127,
"end": 128,
"type": "Text",
"raw": "\n",
"data": "\n"
},
{
"start": 128,
"end": 167,
"type": "Element",
"name": "div",
"attributes": [
{
"start": 133,
"end": 160,
"type": "StyleDirective",
"name": "color",
"value": [
{
"start": 146,
"end": 149,
"type": "Text",
"raw": "red",
"data": "red"
},
{
"start": 149,
"end": 159,
"type": "MustacheTag",
"expression": {
"type": "Identifier",
"start": 150,
"end": 158,
"loc": {
"start": {
"line": 5,
"column": 22
},
"end": {
"line": 5,
"column": 30
}
},
"name": "variable"
}
}
]
}
],
"children": []
},
{
"start": 167,
"end": 168,
"type": "Text",
"raw": "\n",
"data": "\n"
},
{
"start": 168,
"end": 205,
"type": "Element",
"name": "div",
"attributes": [
{
"start": 173,
"end": 198,
"type": "StyleDirective",
"name": "color",
"value": [
{
"start": 185,
"end": 188,
"type": "Text",
"raw": "red",
"data": "red"
},
{
"start": 188,
"end": 198,
"type": "MustacheTag",
"expression": {
"type": "Identifier",
"start": 189,
"end": 197,
"loc": {
"start": {
"line": 6,
"column": 21
},
"end": {
"line": 6,
"column": 29
}
},
"name": "variable"
}
}
]
}
],
"children": []
},
{
"start": 205,
"end": 206,
"type": "Text",
"raw": "\n",
"data": "\n"
},
{
"start": 206,
"end": 252,
"type": "Element",
"name": "div",
"attributes": [
{
"start": 211,
"end": 245,
"type": "StyleDirective",
"name": "color",
"value": [
{
"start": 223,
"end": 245,
"type": "MustacheTag",
"expression": {
"type": "TemplateLiteral",
"start": 224,
"end": 244,
"loc": {
"start": {
"line": 7,
"column": 18
},
"end": {
"line": 7,
"column": 38
}
}, },
"tail": true "expressions": [
{
"type": "Identifier",
"start": 235,
"end": 242,
"loc": {
"start": {
"line": 7,
"column": 29
},
"end": {
"line": 7,
"column": 36
}
},
"name": "literal"
}
],
"quasis": [
{
"type": "TemplateElement",
"start": 225,
"end": 233,
"loc": {
"start": {
"line": 7,
"column": 19
},
"end": {
"line": 7,
"column": 27
}
},
"value": {
"raw": "template",
"cooked": "template"
},
"tail": false
},
{
"type": "TemplateElement",
"start": 243,
"end": 243,
"loc": {
"start": {
"line": 7,
"column": 37
},
"end": {
"line": 7,
"column": 37
}
},
"value": {
"raw": "",
"cooked": ""
},
"tail": true
}
]
} }
] }
} ]
} }
], ],
"children": [] "children": []

@ -13,25 +13,31 @@
{ {
"start": 5, "start": 5,
"end": 26, "end": 26,
"type": "Style", "type": "StyleDirective",
"name": "color", "name": "color",
"modifiers": [], "value": [
"expression": { {
"type": "Identifier", "start": 17,
"start": 18, "end": 26,
"end": 25, "type": "MustacheTag",
"loc": { "expression": {
"start": { "type": "Identifier",
"line": 1, "start": 18,
"column": 18 "end": 25,
}, "loc": {
"end": { "start": {
"line": 1, "line": 1,
"column": 25 "column": 18
},
"end": {
"line": 1,
"column": 25
}
},
"name": "myColor"
} }
}, }
"name": "myColor" ]
}
} }
], ],
"children": [] "children": []

Loading…
Cancel
Save