Skip to content

Commit

Permalink
(refactor) move htmlx2jsx handlers into own files
Browse files Browse the repository at this point in the history
  • Loading branch information
Simon Holthausen committed Sep 3, 2020
1 parent f2468c0 commit 76a1e6a
Show file tree
Hide file tree
Showing 20 changed files with 721 additions and 598 deletions.
643 changes: 55 additions & 588 deletions packages/svelte2tsx/src/htmlxtojsx/index.ts

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions packages/svelte2tsx/src/htmlxtojsx/nodes/action-directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import MagicString from 'magic-string';
import { Node } from 'estree-walker';

/**
* use:xxx ---> {...__sveltets_ensureAction(__sveltets_mapElementTag('ParentNodeName', xxx))}
*/
export function handleActionDirective(
htmlx: string,
str: MagicString,
attr: Node,
parent: Node,
): void {
str.overwrite(
attr.start,
attr.start + 'use:'.length,
`{...__sveltets_ensureAction(__sveltets_mapElementTag('${parent.name}'),`,
);

if (!attr.expression) {
str.appendLeft(attr.end, ')}');
return;
}

str.overwrite(attr.start + `use:${attr.name}`.length, attr.expression.start, ',');
str.appendLeft(attr.expression.end, ')');
if (htmlx[attr.end - 1] == '"') {
str.remove(attr.end - 1, attr.end);
}
}
27 changes: 27 additions & 0 deletions packages/svelte2tsx/src/htmlxtojsx/nodes/animation-directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import MagicString from 'magic-string';
import { Node } from 'estree-walker';

/**
* animation:xxx(yyy) ---> {...__sveltets_ensureAnimation(xxx, yyy)}
*/
export function handleAnimateDirective(htmlx: string, str: MagicString, attr: Node): void {
str.overwrite(
attr.start,
htmlx.indexOf(':', attr.start) + 1,
'{...__sveltets_ensureAnimation(',
);

if (!attr.expression) {
str.appendLeft(attr.end, ', {})}');
return;
}
str.overwrite(
htmlx.indexOf(':', attr.start) + 1 + `${attr.name}`.length,
attr.expression.start,
', ',
);
str.appendLeft(attr.expression.end, ')');
if (htmlx[attr.end - 1] == '"') {
str.remove(attr.end - 1, attr.end);
}
}
167 changes: 167 additions & 0 deletions packages/svelte2tsx/src/htmlxtojsx/nodes/attribute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import MagicString from 'magic-string';
import { Node } from 'estree-walker';
import svgAttributes from '../svgattributes';

/**
* List taken from `svelte-jsx.d.ts` by searching for all attributes of type number
*/
const numberOnlyAttributes = new Set([
'cols',
'colspan',
'currenttime',
'defaultplaybackrate',
'high',
'low',
'marginheight',
'marginwidth',
'minlength',
'maxlength',
'optimum',
'rows',
'rowspan',
'size',
'span',
'start',
'tabindex',
'results',
'volume',
]);

/**
* Handle various kinds of attributes and make them conform to JSX.
* - {x} ---> x={x}
* - x="{..}" ---> x={..}
* - lowercase DOM attributes
* - multi-value handling
*/
export function handleAttribute(htmlx: string, str: MagicString, attr: Node, parent: Node): void {
let transformedFromDirectiveOrNamespace = false;

//if we are on an "element" we are case insensitive, lowercase to match our JSX
if (parent.type == 'Element') {
const sapperNoScroll = attr.name === 'sapper:noscroll';
//skip Attribute shorthand, that is handled below
if (
(attr.value !== true &&
!(
attr.value.length &&
attr.value.length == 1 &&
attr.value[0].type == 'AttributeShorthand'
)) ||
sapperNoScroll
) {
let name = attr.name;
if (!svgAttributes.find((x) => x == name)) {
name = name.toLowerCase();
}

//strip ":" from out attribute name and uppercase the next letter to convert to jsx attribute
const colonIndex = name.indexOf(':');
if (colonIndex >= 0) {
const parts = name.split(':');
name = parts[0] + parts[1][0].toUpperCase() + parts[1].substring(1);
}

str.overwrite(attr.start, attr.start + attr.name.length, name);

transformedFromDirectiveOrNamespace = true;
}
}

//we are a bare attribute
if (attr.value === true) {
if (
parent.type === 'Element' &&
!transformedFromDirectiveOrNamespace &&
parent.name !== '!DOCTYPE'
) {
str.overwrite(attr.start, attr.end, attr.name.toLowerCase());
}
return;
}

if (attr.value.length == 0) return; //wut?
//handle single value
if (attr.value.length == 1) {
const attrVal = attr.value[0];

if (attr.name == 'slot') {
str.remove(attr.start, attr.end);
return;
}

if (attrVal.type == 'AttributeShorthand') {
let attrName = attrVal.expression.name;
if (parent.type == 'Element') {
// eslint-disable-next-line max-len
attrName = svgAttributes.find((a) => a == attrName)
? attrName
: attrName.toLowerCase();
}

str.appendRight(attr.start, `${attrName}=`);
return;
}

const equals = htmlx.lastIndexOf('=', attrVal.start);
if (attrVal.type == 'Text') {
const endsWithQuote =
htmlx.lastIndexOf('"', attrVal.end) === attrVal.end - 1 ||
htmlx.lastIndexOf("'", attrVal.end) === attrVal.end - 1;
const needsQuotes = attrVal.end == attr.end && !endsWithQuote;

const hasBrackets =
htmlx.lastIndexOf('}', attrVal.end) === attrVal.end - 1 ||
htmlx.lastIndexOf('}"', attrVal.end) === attrVal.end - 1 ||
htmlx.lastIndexOf("}'", attrVal.end) === attrVal.end - 1;
const needsNumberConversion =
!hasBrackets &&
parent.type === 'Element' &&
numberOnlyAttributes.has(attr.name.toLowerCase()) &&
!isNaN(attrVal.data);

if (needsNumberConversion) {
if (needsQuotes) {
str.prependRight(equals + 1, '{');
str.appendLeft(attr.end, '}');
} else {
str.overwrite(equals + 1, equals + 2, '{');
str.overwrite(attr.end - 1, attr.end, '}');
}
} else if (needsQuotes) {
str.prependRight(equals + 1, '"');
str.appendLeft(attr.end, '"');
}
return;
}

if (attrVal.type == 'MustacheTag') {
//if the end doesn't line up, we are wrapped in quotes
if (attrVal.end != attr.end) {
str.remove(attrVal.start - 1, attrVal.start);
str.remove(attr.end - 1, attr.end);
}
return;
}
return;
}

// we have multiple attribute values, so we build a string out of them.
// technically the user can do something funky like attr="text "{value} or even attr=text{value}
// so instead of trying to maintain a nice sourcemap with prepends etc, we just overwrite the whole thing

const equals = htmlx.lastIndexOf('=', attr.value[0].start);
str.overwrite(equals, attr.value[0].start, '={`');

for (const n of attr.value) {
if (n.type == 'MustacheTag') {
str.appendRight(n.start, '$');
}
}

if (htmlx[attr.end - 1] == '"') {
str.overwrite(attr.end - 1, attr.end, '`}');
} else {
str.appendLeft(attr.end, '`}');
}
}
82 changes: 82 additions & 0 deletions packages/svelte2tsx/src/htmlxtojsx/nodes/binding.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import MagicString from 'magic-string';
import { Node } from 'estree-walker';
import { isShortHandAttribute, getThisType } from '../utils/node-utils';

const oneWayBindingAttributes: Map<string, string> = new Map(
['clientWidth', 'clientHeight', 'offsetWidth', 'offsetHeight']
.map((e) => [e, 'HTMLDivElement'] as [string, string])
.concat(
['duration', 'buffered', 'seekable', 'seeking', 'played', 'ended'].map((e) => [
e,
'HTMLMediaElement',
]),
),
);

/**
* Transform bind:xxx into something that conforms to JSX
*/
export function handleBinding(htmlx: string, str: MagicString, attr: Node, el: Node): void {
//bind group on input
if (attr.name == 'group' && el.name == 'input') {
str.remove(attr.start, attr.expression.start);
str.appendLeft(attr.expression.start, '{...__sveltets_empty(');

const endBrackets = ')}';
if (isShortHandAttribute(attr)) {
str.prependRight(attr.end, endBrackets);
} else {
str.overwrite(attr.expression.end, attr.end, endBrackets);
}
return;
}

const supportsBindThis = ['InlineComponent', 'Element', 'Body'];

//bind this
if (attr.name === 'this' && supportsBindThis.includes(el.type)) {
const thisType = getThisType(el);

if (thisType) {
str.remove(attr.start, attr.expression.start);
str.appendLeft(attr.expression.start, `{...__sveltets_ensureType(${thisType}, `);
str.overwrite(attr.expression.end, attr.end, ')}');
return;
}
}

//one way binding
if (oneWayBindingAttributes.has(attr.name) && el.type === 'Element') {
str.remove(attr.start, attr.expression.start);
str.appendLeft(attr.expression.start, `{...__sveltets_empty(`);
if (isShortHandAttribute(attr)) {
// eslint-disable-next-line max-len
str.appendLeft(
attr.end,
`=__sveltets_instanceOf(${oneWayBindingAttributes.get(attr.name)}).${attr.name})}`,
);
} else {
// eslint-disable-next-line max-len
str.overwrite(
attr.expression.end,
attr.end,
`=__sveltets_instanceOf(${oneWayBindingAttributes.get(attr.name)}).${attr.name})}`,
);
}
return;
}

str.remove(attr.start, attr.start + 'bind:'.length);
if (attr.expression.start === attr.start + 'bind:'.length) {
str.prependLeft(attr.expression.start, `${attr.name}={`);
str.appendLeft(attr.end, `}`);
return;
}

//remove possible quotes
if (htmlx[attr.end - 1] === '"') {
const firstQuote = htmlx.indexOf('"', attr.start);
str.remove(firstQuote, firstQuote + 1);
str.remove(attr.end - 1, attr.end);
}
}
15 changes: 15 additions & 0 deletions packages/svelte2tsx/src/htmlxtojsx/nodes/class-directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import MagicString from 'magic-string';
import { Node } from 'estree-walker';

/**
* class:xx={yyy} ---> {...__sveltets_ensureType(Boolean, !!(yyy))}
*/
export function handleClassDirective(str: MagicString, attr: Node): void {
str.overwrite(attr.start, attr.expression.start, `{...__sveltets_ensureType(Boolean, !!(`);
const endBrackets = `))}`;
if (attr.end !== attr.expression.end) {
str.overwrite(attr.expression.end, attr.end, endBrackets);
} else {
str.appendLeft(attr.end, endBrackets);
}
}
9 changes: 9 additions & 0 deletions packages/svelte2tsx/src/htmlxtojsx/nodes/comment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import MagicString from 'magic-string';
import { Node } from 'estree-walker';

/**
* Removes comment
*/
export function handleComment(str: MagicString, node: Node): void {
str.remove(node.start, node.end);
}
9 changes: 0 additions & 9 deletions packages/svelte2tsx/src/htmlxtojsx/nodes/component-type.ts

This file was deleted.

Loading

0 comments on commit 76a1e6a

Please sign in to comment.