add typescript support to parser

pull/9482/head
Rich Harris 3 years ago
parent 555e8f845f
commit eee54a28ff

@ -116,6 +116,7 @@
"@ampproject/remapping": "^2.2.1",
"@jridgewell/sourcemap-codec": "^1.4.15",
"acorn": "^8.10.0",
"acorn-typescript": "^1.4.11",
"aria-query": "^5.3.0",
"axobject-query": "^4.0.0",
"esm-env": "^1.0.0",

@ -1,19 +1,32 @@
import * as acorn from 'acorn';
import { walk } from 'zimmerframe';
import { tsPlugin } from 'acorn-typescript';
import { strip_types } from './strip-types.js';
// @ts-expect-error
const ParserWithTS = acorn.Parser.extend(tsPlugin());
/**
* @param {string} source
*/
export function parse(source) {
const { onComment, add_comments } = get_comment_handlers(source);
const ast = acorn.parse(source, {
onComment,
sourceType: 'module',
ecmaVersion: 13,
locations: true
});
const ast = /** @type {import('estree').Program} */ (
strip_types(
// @ts-expect-error
ParserWithTS.parse(source, {
onComment,
sourceType: 'module',
ecmaVersion: 13,
locations: true
})
)
);
// @ts-expect-error
add_comments(ast);
return /** @type {import('estree').Program} */ (ast);
return ast;
}
/**
@ -22,14 +35,22 @@ export function parse(source) {
*/
export function parse_expression_at(source, index) {
const { onComment, add_comments } = get_comment_handlers(source);
const ast = acorn.parseExpressionAt(source, index, {
onComment,
sourceType: 'module',
ecmaVersion: 13,
locations: true
});
const ast = /** @type {import('estree').Expression} */ (
strip_types(
// @ts-expect-error
ParserWithTS.parseExpressionAt(source, index, {
onComment,
sourceType: 'module',
ecmaVersion: 13,
locations: true
})
)
);
// @ts-expect-error
add_comments(ast);
return /** @type {import('estree').Expression} */ (ast);
return ast;
}
/**

@ -67,10 +67,55 @@ function open(parser) {
if (parser.eat('each')) {
parser.require_whitespace();
const expression = read_expression(parser);
const template = parser.template;
let end = parser.template.length;
/** @type {import('estree').Expression | undefined} */
let expression;
// we have to do this loop because `{#each x as { y = z }}` fails to parse —
// the `as { y = z }` is treated as an Expression but it's actually a Pattern.
// the 'fix' is to backtrack and hide everything from the `as` onwards, until
// we get a valid expression
while (!expression) {
try {
expression = read_expression(parser);
} catch (err) {
end = /** @type {any} */ (err).position[0] - 2;
while (end > start && parser.template.slice(end, end + 2) !== 'as') {
end -= 1;
}
if (end <= start) throw err;
// @ts-expect-error parser.template is meant to be readonly, this is a special case
parser.template = template.slice(0, end);
}
}
// @ts-expect-error
parser.template = template;
parser.allow_whitespace();
// {#each} blocks must declare a context {#each list as item}
if (!parser.match('as')) {
// this could be a TypeScript assertion that was erroneously eaten.
if (expression.type === 'SequenceExpression') {
expression = expression.expressions[0];
}
// @ts-expect-error 'TSAsExpression' is not recognized as an estree type
if (expression.type === 'TSAsExpression') {
expression = /** @type {import('estree').Expression} */ (
/** @type {any} */ (expression).expression
);
parser.index = /** @type {number} */ (expression.end);
parser.allow_whitespace();
}
}
parser.eat('as', true);
parser.require_whitespace();

@ -0,0 +1,48 @@
import { walk } from 'zimmerframe';
import * as b from '../../utils/builders.js';
/**
* @param {import('estree').Node} node
*/
export function strip_types(node) {
return walk(node, null, {
_(node, context) {
// @ts-expect-error
if (node.exportKind === 'type' || node.importKind === 'type') {
console.log('>>>', node);
return b.empty;
}
// @ts-expect-error
delete node.loc.start.index;
// @ts-expect-error
delete node.loc.end.index;
// // @ts-expect-error
// delete node.returnType;
// // @ts-expect-error
// delete node.importKind;
// // @ts-expect-error
// delete node.exportKind;
// // @ts-expect-error
// delete node.typeAnnotation;
// // @ts-expect-error
// delete node.typeParameters;
// if (node.type.startsWith('TS')) {
// const ts_node = /** @type {any} */ (node);
// switch (ts_node.type) {
// case 'TSAsExpression':
// case 'TSNonNullExpression':
// // hack to make sure parser skips over the type assertion
// ts_node.expression.end = ts_node.end;
// return context.visit(ts_node.expression);
// default:
// return b.empty;
// }
// }
context.next();
}
});
}

@ -27,6 +27,7 @@
"body": [
{
"type": "ImportDeclaration",
"importKind": "value",
"start": 10,
"end": 43,
"loc": {
@ -42,6 +43,7 @@
"specifiers": [
{
"type": "ImportSpecifier",
"importKind": "value",
"start": 19,
"end": 26,
"loc": {

@ -196,6 +196,7 @@
"body": [
{
"type": "ExportNamedDeclaration",
"exportKind": "value",
"start": 10,
"end": 29,
"loc": {

@ -0,0 +1,3 @@
{#each people as { name, cool = true }}
<p>{name} is {cool ? 'cool' : 'not cool'}</p>
{/each}

@ -0,0 +1,317 @@
{
"css": null,
"js": [],
"start": 0,
"end": 94,
"type": "Root",
"fragment": {
"type": "Fragment",
"nodes": [
{
"type": "EachBlock",
"start": 0,
"end": 94,
"expression": {
"type": "Identifier",
"start": 7,
"end": 13,
"loc": {
"start": {
"line": 1,
"column": 7
},
"end": {
"line": 1,
"column": 13
}
},
"name": "people"
},
"body": {
"type": "Fragment",
"nodes": [
{
"type": "Text",
"start": 39,
"end": 41,
"raw": "\n\t",
"data": "\n\t"
},
{
"type": "RegularElement",
"start": 41,
"end": 86,
"name": "p",
"attributes": [],
"fragment": {
"type": "Fragment",
"nodes": [
{
"type": "ExpressionTag",
"start": 44,
"end": 50,
"expression": {
"type": "Identifier",
"start": 45,
"end": 49,
"loc": {
"start": {
"line": 2,
"column": 5
},
"end": {
"line": 2,
"column": 9
}
},
"name": "name"
}
},
{
"type": "Text",
"start": 50,
"end": 54,
"raw": " is ",
"data": " is "
},
{
"type": "ExpressionTag",
"start": 54,
"end": 82,
"expression": {
"type": "ConditionalExpression",
"start": 55,
"end": 81,
"loc": {
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 2,
"column": 41
}
},
"test": {
"type": "Identifier",
"start": 55,
"end": 59,
"loc": {
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 2,
"column": 19
}
},
"name": "cool"
},
"consequent": {
"type": "Literal",
"start": 62,
"end": 68,
"loc": {
"start": {
"line": 2,
"column": 22
},
"end": {
"line": 2,
"column": 28
}
},
"value": "cool",
"raw": "'cool'"
},
"alternate": {
"type": "Literal",
"start": 71,
"end": 81,
"loc": {
"start": {
"line": 2,
"column": 31
},
"end": {
"line": 2,
"column": 41
}
},
"value": "not cool",
"raw": "'not cool'"
}
}
}
],
"transparent": true
}
},
{
"type": "Text",
"start": 86,
"end": 87,
"raw": "\n",
"data": "\n"
}
],
"transparent": false
},
"context": {
"type": "ObjectPattern",
"start": 17,
"end": 38,
"loc": {
"start": {
"line": 1,
"column": 17
},
"end": {
"line": 1,
"column": 38
}
},
"properties": [
{
"type": "Property",
"start": 19,
"end": 23,
"loc": {
"start": {
"line": 1,
"column": 19
},
"end": {
"line": 1,
"column": 23
}
},
"method": false,
"shorthand": true,
"computed": false,
"key": {
"type": "Identifier",
"start": 19,
"end": 23,
"loc": {
"start": {
"line": 1,
"column": 19
},
"end": {
"line": 1,
"column": 23
}
},
"name": "name"
},
"kind": "init",
"value": {
"type": "Identifier",
"start": 19,
"end": 23,
"loc": {
"start": {
"line": 1,
"column": 19
},
"end": {
"line": 1,
"column": 23
}
},
"name": "name"
}
},
{
"type": "Property",
"start": 25,
"end": 36,
"loc": {
"start": {
"line": 1,
"column": 25
},
"end": {
"line": 1,
"column": 36
}
},
"method": false,
"shorthand": true,
"computed": false,
"key": {
"type": "Identifier",
"start": 25,
"end": 29,
"loc": {
"start": {
"line": 1,
"column": 25
},
"end": {
"line": 1,
"column": 29
}
},
"name": "cool"
},
"kind": "init",
"value": {
"type": "AssignmentPattern",
"start": 25,
"end": 36,
"loc": {
"start": {
"line": 1,
"column": 25
},
"end": {
"line": 1,
"column": 36
}
},
"left": {
"type": "Identifier",
"start": 25,
"end": 29,
"loc": {
"start": {
"line": 1,
"column": 25
},
"end": {
"line": 1,
"column": 29
}
},
"name": "cool"
},
"right": {
"type": "Literal",
"start": 32,
"end": 36,
"loc": {
"start": {
"line": 1,
"column": 32
},
"end": {
"line": 1,
"column": 36
}
},
"value": true,
"raw": "true"
}
}
}
]
}
}
],
"transparent": false
},
"options": null
}

@ -10,6 +10,7 @@
"noErrorTruncation": true,
"allowSyntheticDefaultImports": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true,
"types": ["node"],
"strict": true,
"allowJs": true,

@ -68,6 +68,9 @@ importers:
acorn:
specifier: ^8.10.0
version: 8.11.2
acorn-typescript:
specifier: ^1.4.11
version: 1.4.11(acorn@8.11.2)
aria-query:
specifier: ^5.3.0
version: 5.3.0
@ -2746,6 +2749,14 @@ packages:
acorn: 8.11.2
dev: true
/acorn-typescript@1.4.11(acorn@8.11.2):
resolution: {integrity: sha512-cRGgp+4HMxMZAiMS61ZmQ3iuU/+A4g4ZYZsyLZdmvrEVN/TOwfJ40rPWcLqi3H5ut75SYAdOOJj6QGCcrkK57w==}
peerDependencies:
acorn: '>=8.9.0'
dependencies:
acorn: 8.11.2
dev: false
/acorn-walk@8.3.0:
resolution: {integrity: sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==}
engines: {node: '>=0.4.0'}

Loading…
Cancel
Save