Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
295 changes: 143 additions & 152 deletions src/transform-microsyntax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,71 @@ import type {
RawNGSpan,
} from './types.js';
import { NG_PARSE_TEMPLATE_BINDINGS_FAKE_PREFIX } from './parser.js';
import { toLowerCamelCase, transformSpan } from './utils.js';
import { toLowerCamelCase, transformSpan, createNode } from './utils.js';

function isExpressionBinding(
templateBinding: ng.TemplateBinding,
): templateBinding is ng.ExpressionBinding {
return templateBinding instanceof NGExpressionBinding;
}

function isVariableBinding(
templateBinding: ng.TemplateBinding,
): templateBinding is ng.VariableBinding {
return templateBinding instanceof NGVariableBinding;
}

/**
* - "a" (start=0 end=1) -> (start=0 end=3)
* - '\'' (start=0 end=1) -> (start=0 end=4)
*/
function fixSpan(span: RawNGSpan, text: string) {
if (text[span.start] !== '"' && text[span.start] !== "'") {
return;
}
const quote = text[span.start];
let hasBackSlash = false;
for (let i = span.start + 1; i < text.length; i++) {
switch (text[i]) {
case quote:
if (!hasBackSlash) {
span.end = i + 1;
return;
}
// fall through
default:
hasBackSlash = false;
break;
case '\\':
hasBackSlash = !hasBackSlash;
break;
}
}
}

/**
* - "as b" (value="NgEstreeParser" key="b") -> (value="$implicit" key="b")
*/
function getAsVariableBindingValue(
variableBinding: ng.VariableBinding,
context: Context,
): ng.VariableBinding['value'] {
if (
!variableBinding.value ||
variableBinding.value.source !== NG_PARSE_TEMPLATE_BINDINGS_FAKE_PREFIX
) {
return variableBinding.value;
}

const index = context.getCharacterIndex(
/\S/,
variableBinding.sourceSpan.start,
);
return {
source: '$implicit',
span: { start: index, end: index },
};
}

function transformTemplateBindings({
expressions: rawTemplateBindings,
Expand Down Expand Up @@ -53,11 +117,11 @@ function transformTemplateBindings({
templateBinding.value &&
templateBinding.value.source === lastTemplateBinding.key.source
) {
const alias = _c<NGMicrosyntaxKey>(
'NGMicrosyntaxKey',
{ name: templateBinding.key.source },
templateBinding.key.span,
);
const alias = _c<NGMicrosyntaxKey>({
type: 'NGMicrosyntaxKey',
name: templateBinding.key.source,
...templateBinding.key.span,
});
const updateSpanEnd = <T extends NGNode>(node: T, end: number): T => ({
...node,
...transformSpan({ start: node.start!, end }, context.text),
Expand All @@ -84,13 +148,13 @@ function transformTemplateBindings({
lastTemplateBinding = templateBinding;
}

return _c<NGMicrosyntax>(
'NGMicrosyntax',
{ body },
body.length === 0
return _c<NGMicrosyntax>({
type: 'NGMicrosyntax',
body,
...(body.length === 0
? rawTemplateBindings[0].sourceSpan
: { start: body[0].start, end: body.at(-1)!.end },
);
: { start: body[0].start, end: body.at(-1)!.end }),
});

function transformTemplateBinding(
templateBinding: ng.TemplateBinding,
Expand All @@ -99,34 +163,35 @@ function transformTemplateBindings({
if (isExpressionBinding(templateBinding)) {
const { key, value } = templateBinding;
if (!value) {
return _c<NGMicrosyntaxKey>(
'NGMicrosyntaxKey',
{ name: removePrefix(key.source) },
key.span,
);
return _c<NGMicrosyntaxKey>({
type: 'NGMicrosyntaxKey',
name: removePrefix(key.source),
...key.span,
});
} else if (index === 0) {
return _c<NGMicrosyntaxExpression>(
'NGMicrosyntaxExpression',
{ expression: _t<NGNode>(value.ast), alias: null },
value.sourceSpan,
);
return _c<NGMicrosyntaxExpression>({
type: 'NGMicrosyntaxExpression',
expression: _t<NGNode>(value.ast),
alias: null,
...value.sourceSpan,
});
} else {
return _c<NGMicrosyntaxKeyedExpression>(
'NGMicrosyntaxKeyedExpression',
{
key: _c<NGMicrosyntaxKey>(
'NGMicrosyntaxKey',
{ name: removePrefix(key.source) },
key.span,
),
expression: _c<NGMicrosyntaxExpression>(
'NGMicrosyntaxExpression',
{ expression: _t<NGNode>(value.ast), alias: null },
value.sourceSpan,
),
},
{ start: key.span.start, end: value.sourceSpan.end },
);
return _c<NGMicrosyntaxKeyedExpression>({
type: 'NGMicrosyntaxKeyedExpression',
key: _c<NGMicrosyntaxKey>({
type: 'NGMicrosyntaxKey',
name: removePrefix(key.source),
...key.span,
}),
expression: _c<NGMicrosyntaxExpression>({
type: 'NGMicrosyntaxExpression',
expression: _t<NGNode>(value.ast),
alias: null,
...value.sourceSpan,
}),
start: key.span.start,
end: value.sourceSpan.end,
});
}
} else {
const { key, sourceSpan } = templateBinding;
Expand All @@ -135,138 +200,64 @@ function transformTemplateBindings({
);
if (startsWithLet) {
const { value } = templateBinding;
return _c<NGMicrosyntaxLet>(
'NGMicrosyntaxLet',
{
key: _c<NGMicrosyntaxKey>(
'NGMicrosyntaxKey',
{ name: key.source },
key.span,
),
value: !value
? null
: _c<NGMicrosyntaxKey>(
'NGMicrosyntaxKey',
{ name: value.source },
value.span,
),
},
{
start: sourceSpan.start,
end: value ? value.span.end : key.span.end,
},
);
return _c<NGMicrosyntaxLet>({
type: 'NGMicrosyntaxLet',
key: _c<NGMicrosyntaxKey>({
type: 'NGMicrosyntaxKey',
name: key.source,
...key.span,
}),
value: !value
? null
: _c<NGMicrosyntaxKey>({
type: 'NGMicrosyntaxKey',
name: value.source,
...value.span,
}),
start: sourceSpan.start,
end: value ? value.span.end : key.span.end,
});
} else {
const value = getAsVariableBindingValue(templateBinding);
return _c<NGMicrosyntaxAs>(
'NGMicrosyntaxAs',
{
key: _c<NGMicrosyntaxKey>(
'NGMicrosyntaxKey',
{ name: value!.source },
value!.span,
),
alias: _c<NGMicrosyntaxKey>(
'NGMicrosyntaxKey',
{ name: key.source },
key.span,
),
},
{ start: value!.span.start, end: key.span.end },
);
const value = getAsVariableBindingValue(templateBinding, context);
return _c<NGMicrosyntaxAs>({
type: 'NGMicrosyntaxAs',
key: _c<NGMicrosyntaxKey>({
type: 'NGMicrosyntaxKey',
name: value!.source,
...value!.span,
}),
alias: _c<NGMicrosyntaxKey>({
type: 'NGMicrosyntaxKey',
name: key.source,
...key.span,
}),
start: value!.span.start,
end: key.span.end,
});
}
}
}

function _t<T extends NGNode>(n: ng.AST) {
return transformNode(n, context) as T;
function _t<T extends NGNode>(node: ng.AST) {
return transformNode(node, context) as T;
}

function _c<T extends NGNode>(
t: T['type'],
n: Partial<T>,
span: RawNGSpan,
stripSpaces = true,
properties: Partial<T> & { type: T['type'] } & RawNGSpan,
{ stripSpaces = true } = {},
) {
return {
type: t,
...transformSpan(span, context.text, { processSpan: stripSpaces }),
...n,
} as T;
return createNode<T>(context, properties, { stripSpaces });
}

function removePrefix(string: string) {
return toLowerCamelCase(string.slice(prefix.source.length));
}

function isExpressionBinding(
templateBinding: ng.TemplateBinding,
): templateBinding is ng.ExpressionBinding {
return templateBinding instanceof NGExpressionBinding;
}

function isVariableBinding(
templateBinding: ng.TemplateBinding,
): templateBinding is ng.VariableBinding {
return templateBinding instanceof NGVariableBinding;
}

function fixTemplateBindingSpan(templateBinding: ng.TemplateBinding) {
fixSpan(templateBinding.key.span);
fixSpan(templateBinding.key.span, context.text);
if (isVariableBinding(templateBinding) && templateBinding.value) {
fixSpan(templateBinding.value.span);
}
}

/**
* - "a" (start=0 end=1) -> (start=0 end=3)
* - '\'' (start=0 end=1) -> (start=0 end=4)
*/
function fixSpan(span: RawNGSpan) {
if (context.text[span.start] !== '"' && context.text[span.start] !== "'") {
return;
fixSpan(templateBinding.value.span, context.text);
}
const quote = context.text[span.start];
let hasBackSlash = false;
for (let i = span.start + 1; i < context.text.length; i++) {
switch (context.text[i]) {
case quote:
if (!hasBackSlash) {
span.end = i + 1;
return;
}
// fall through
default:
hasBackSlash = false;
break;
case '\\':
hasBackSlash = !hasBackSlash;
break;
}
}
}

/**
* - "as b" (value="NgEstreeParser" key="b") -> (value="$implicit" key="b")
*/
function getAsVariableBindingValue(
variableBinding: ng.VariableBinding,
): ng.VariableBinding['value'] {
if (
!variableBinding.value ||
variableBinding.value.source !== NG_PARSE_TEMPLATE_BINDINGS_FAKE_PREFIX
) {
return variableBinding.value;
}

const index = context.getCharacterIndex(
/\S/,
variableBinding.sourceSpan.start,
);
return {
source: '$implicit',
span: { start: index, end: index },
};
}
}

Expand Down
Loading