Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(DRAFT): use acorn, estree and esrap #100

Closed
wants to merge 4 commits into from
Closed
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
4 changes: 2 additions & 2 deletions packages/adders/drizzle/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,8 @@ export default defineAdder({
url: common.expressionFromString('process.env.DATABASE_URL'),
authToken
}),
verbose: { type: 'BooleanLiteral', value: true },
strict: { type: 'BooleanLiteral', value: true },
verbose: { type: 'Literal', value: true },
strict: { type: 'Literal', value: true },
driver
});

Expand Down
3 changes: 1 addition & 2 deletions packages/adders/eslint/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
functions,
imports,
object,
type AstKinds,
type AstTypes
} from '@sveltejs/cli-core/js';
import { parseJson, parseScript } from '@sveltejs/cli-core/parsers';
Expand Down Expand Up @@ -71,7 +70,7 @@ export default defineAdder({
const { ast, generateCode } = parseScript(content);

const eslintConfigs: Array<
AstKinds.ExpressionKind | AstTypes.SpreadElement | AstTypes.ObjectExpression
AstTypes.Expression | AstTypes.SpreadElement | AstTypes.ObjectExpression
> = [];

const jsConfig = common.expressionFromString('js.configs.recommended');
Expand Down
17 changes: 9 additions & 8 deletions packages/adders/lucia/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,17 @@ export default defineAdder({
name: ({ typescript }) => `drizzle.config.${typescript ? 'ts' : 'js'}`,
content: ({ content }) => {
const { ast, generateCode } = parseScript(content);
const isProp = (name: string, node: AstTypes.ObjectProperty) =>
const isProp = (name: string, node: AstTypes.Property) =>
node.key.type === 'Identifier' && node.key.name === name;

// prettier-ignore
Walker.walk(ast as AstTypes.ASTNode, {}, {
ObjectProperty(node) {
if (isProp('dialect', node) && node.value.type === 'StringLiteral') {
Walker.walk(ast as AstTypes.Node, {}, {
Property(node) {
if (isProp('dialect', node) && node.value.type === 'Literal') {
drizzleDialect = node.value.value as Dialect;
}
if (isProp('schema', node) && node.value.type === 'StringLiteral') {
schemaPath = node.value.value;
if (isProp('schema', node) && node.value.type === 'Literal') {
schemaPath = node.value.value as string;
}
}
})
Expand Down Expand Up @@ -549,14 +549,15 @@ function createLuciaType(name: string): AstTypes.TSInterfaceBody['body'][number]
type: 'Identifier',
name
},
computed: false,
typeAnnotation: {
type: 'TSTypeAnnotation',
typeAnnotation: {
type: 'TSUnionType',
types: [
{
type: 'TSImportType',
argument: { type: 'StringLiteral', value: 'lucia' },
argument: { type: 'Literal', value: 'lucia' },
qualifier: {
type: 'Identifier',
// capitalize first letter
Expand Down Expand Up @@ -603,7 +604,7 @@ function getAuthHandleContent() {
};`;
}

function getCallExpression(ast: AstTypes.ASTNode): AstTypes.CallExpression | undefined {
function getCallExpression(ast: AstTypes.Node): AstTypes.CallExpression | undefined {
let callExpression;

// prettier-ignore
Expand Down
14 changes: 11 additions & 3 deletions packages/adders/vitest/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ export default defineAdder({
importDecl.importKind === 'value' &&
importDecl.specifiers?.some(
(specifier) =>
specifier.type === 'ImportSpecifier' && specifier.imported.name === 'defineConfig'
specifier.type === 'ImportSpecifier' &&
specifier.imported.type == 'Identifier' &&
specifier.imported.name === 'defineConfig'
)
);

Expand All @@ -67,7 +69,10 @@ export default defineAdder({
} else {
// otherwise, just remove the `defineConfig` specifier
const idxToRemove = defineConfigImportDecl?.specifiers?.findIndex(
(s) => s.type === 'ImportSpecifier' && s.imported.name === 'defineConfig'
(specifier) =>
specifier.type === 'ImportSpecifier' &&
specifier.imported.type == 'Identifier' &&
specifier.imported.name === 'defineConfig'
);
if (idxToRemove) defineConfigImportDecl?.specifiers?.splice(idxToRemove, 1);
}
Expand All @@ -86,7 +91,10 @@ export default defineAdder({
) {
// if the previous `defineConfig` was aliased, reuse the alias for the "vitest/config" import
const importSpecifier = defineConfigImportDecl?.specifiers?.find(
(sp) => sp.type === 'ImportSpecifier' && sp.imported.name === 'defineConfig'
(specifier) =>
specifier.type === 'ImportSpecifier' &&
specifier.imported.type == 'Identifier' &&
specifier.imported.name === 'defineConfig'
);
const defineConfigAlias = importSpecifier?.local?.name ?? 'defineConfig';
imports.addNamed(ast, 'vitest/config', { defineConfig: defineConfigAlias });
Expand Down
74 changes: 60 additions & 14 deletions packages/ast-tooling/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { parse as tsParse } from 'recast/parsers/typescript.js';
import { parse as recastParse, print as recastPrint } from 'recast';
import { Document, Element, Text, type ChildNode } from 'domhandler';
import { ElementType, parseDocument } from 'htmlparser2';
import { appendChild, prependChild, removeElement, textContent } from 'domutils';
Expand All @@ -15,8 +13,11 @@ import {
} from 'postcss';
import * as fleece from 'silver-fleece';
import * as Walker from 'zimmerframe';
import type { namedTypes as AstTypes } from 'ast-types';
import type * as AstKinds from 'ast-types/gen/kinds.d.ts';
import * as acorn from 'acorn';
import { tsPlugin } from 'acorn-typescript';
import { print as esrapPrint } from 'esrap-typescript-temp';
// todo: why is this file only generated during `dev` startup, if it's prefixed with type?
import { TsEstree } from './ts-estree.ts';

/**
* Most of the AST tooling is pretty big in bundle size and bundling takes forever.
Expand Down Expand Up @@ -48,25 +49,70 @@ export type {
ChildNode as HtmlChildNode,

// js
AstTypes,
AstKinds,
TsEstree as AstTypes,

//css
CssChildNode
};

export function parseScript(content: string): AstTypes.Program {
const recastOutput: { program: AstTypes.Program } = recastParse(content, {
parser: {
parse: tsParse
export function parseScript(content: string): TsEstree.Program {
const comments: any[] = [];

// @ts-expect-error
const acornTs = acorn.Parser.extend(tsPlugin({ allowSatisfies: true }));

const ast = acornTs.parse(content, {
ecmaVersion: 'latest',
sourceType: 'module',
locations: true,
onComment: (block, value, start, end) => {
if (block && /\n/.test(value)) {
let a = start;
while (a > 0 && content[a - 1] !== '\n') a -= 1;

let b = a;
// @ts-expect-error
while (/[ \t]/.test(content[b])) b += 1;

const indentation = content.slice(a, b);
value = value.replace(new RegExp(`^${indentation}`, 'gm'), '');
}

comments.push({ type: block ? 'Block' : 'Line', value, start, end });
}
});

Walker.walk(ast, null, {
_(node, { next }) {
const commentNode /** @type {import('../../src/types').NodeWithComments} */ =
/** @type {any} */ node;
let comment;

while (comments[0] && comments[0].start < node.start) {
comment = comments.shift();
// @ts-expect-error
(commentNode.leadingComments ||= []).push(comment);
}

next();

if (comments[0]) {
const slice = content.slice(node.end, comments[0].start);

if (/^[,) \t]*$/.test(slice)) {
// @ts-expect-error
commentNode.trailingComments = [comments.shift()];
}
}
}
});

return recastOutput.program;
return ast as TsEstree.Program;
}

export function serializeScript(ast: AstTypes.ASTNode): string {
return recastPrint(ast).code;
export function serializeScript(ast: TsEstree.Node): string {
const { code } = esrapPrint(ast, {});
return code;
}

export function parseCss(content: string): CssAst {
Expand Down Expand Up @@ -108,7 +154,7 @@ export function stripAst<T>(node: T, propToRemove: string): T {
}

export type SvelteAst = {
jsAst: AstTypes.Program;
jsAst: TsEstree.Program;
htmlAst: Document;
cssAst: CssAst;
};
Expand Down
7 changes: 4 additions & 3 deletions packages/ast-tooling/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@
"dist"
],
"devDependencies": {
"@babel/parser": "^7.24.7",
"ast-types": "^0.14.2",
"@types/estree": "^1.0.6",
"acorn": "^8.12.1",
"acorn-typescript": "^1.4.13",
"dom-serializer": "^2.0.0",
"domhandler": "^5.0.3",
"domutils": "^3.1.0",
"esrap-typescript-temp": "^0.0.1",
"htmlparser2": "^9.1.0",
"postcss": "^8.4.38",
"recast": "^0.23.7",
"silver-fleece": "^1.1.0",
"zimmerframe": "^1.1.2"
},
Expand Down
79 changes: 79 additions & 0 deletions packages/ast-tooling/ts-estree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import type * as estree from 'estree';

declare module 'estree' {
// new types
interface TSTypeAnnotation {
type: 'TSTypeAnnotation';
typeAnnotation: TSStringKeyword | TSTypeReference | TSUnionType;
}
interface TSStringKeyword {
type: 'TSStringKeyword';
}
interface TSNullKeyword {
type: 'TSNullKeyword';
}
interface TSTypeReference {
type: 'TSTypeReference';
typeName: Identifier;
}
interface TSAsExpression extends BaseNode {
type: 'TSAsExpression';
expression: Expression;
typeAnnotation: TSTypeAnnotation['typeAnnotation'];
}
interface TSModuleDeclaration extends BaseNode {
type: 'TSModuleDeclaration';
global: boolean;
declare: boolean;
id: Identifier;
body: TSModuleBlock;
}
interface TSModuleBlock extends BaseNode {
type: 'TSModuleBlock';
body: Array<TSModuleDeclaration | TSInterfaceDeclaration>;
}
interface TSInterfaceDeclaration extends BaseNode {
type: 'TSInterfaceDeclaration';
id: Identifier;
body: TSInterfaceBody;
}
interface TSInterfaceBody extends BaseNode {
type: 'TSInterfaceBody';
body: TSPropertySignature[];
}
interface TSPropertySignature extends BaseNode {
type: 'TSPropertySignature';
computed: boolean;
key: Identifier;
typeAnnotation: TSTypeAnnotation;
}
interface TSProgram extends Omit<Program, 'body'> {
body: Array<Directive | Statement | ModuleDeclaration | TSModuleDeclaration>;
}
interface TSUnionType {
type: 'TSUnionType';
types: Array<TSNullKeyword | TSTypeReference | TSImportType>;
}
interface TSImportType {
type: 'TSImportType';
argument: Literal;
qualifier: Identifier;
}

// enhanced types
interface Identifier {
typeAnnotation?: TSTypeAnnotation;
}
interface ExpressionMap {
TSAsExpression: TSAsExpression;
}
interface NodeMap {
TSModuleDeclaration: TSModuleDeclaration;
TSInterfaceDeclaration: TSInterfaceDeclaration;
}
interface ImportDeclaration {
importKind: 'type' | 'value';
}
}

export type { estree as TsEstree };
4 changes: 2 additions & 2 deletions packages/core/tests/css/common/add-at-rule/run.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { addAtRule } from '@sveltejs/cli-core/css';
import type { CssFileEditor } from '@sveltejs/cli-core';
import type { CssAst } from '@sveltejs/ast-tooling';

export function run({ ast }: CssFileEditor<any>): void {
export function run(ast: CssAst): void {
addAtRule(ast, 'tailwind', "'lib/path/file.ext'", false);
addAtRule(ast, 'tailwind', "'lib/path/file1.ext'", true);
}
4 changes: 2 additions & 2 deletions packages/core/tests/css/common/add-comment/run.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { addComment } from '@sveltejs/cli-core/css';
import type { CssFileEditor } from '@sveltejs/cli-core';
import type { CssAst } from '@sveltejs/ast-tooling';

export function run({ ast }: CssFileEditor<any>): void {
export function run(ast: CssAst): void {
addComment(ast, 'foo comment');
}
4 changes: 2 additions & 2 deletions packages/core/tests/css/common/add-imports/run.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { addImports } from '@sveltejs/cli-core/css';
import type { CssFileEditor } from '@sveltejs/cli-core';
import type { CssAst } from '@sveltejs/ast-tooling';

export function run({ ast }: CssFileEditor<any>): void {
export function run(ast: CssAst): void {
addImports(ast, ["'lib/path/file.css'"]);
}
4 changes: 2 additions & 2 deletions packages/core/tests/css/common/add-rule/run.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { addDeclaration, addRule } from '@sveltejs/cli-core/css';
import type { CssFileEditor } from '@sveltejs/cli-core';
import type { CssAst } from '@sveltejs/ast-tooling';

export function run({ ast }: CssFileEditor<any>): void {
export function run(ast: CssAst): void {
const barSelectorRule = addRule(ast, '.bar');
addDeclaration(barSelectorRule, 'color', 'blue');
}
2 changes: 1 addition & 1 deletion packages/core/tests/css/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ for (const categoryDirectory of categoryDirectories) {

// dynamic imports always need to provide the path inline for static analysis
const module = await import(`./${categoryDirectory}/${testName}/run.ts`);
module.run({ ast });
module.run(ast);

const output = serializeCss(ast);
await expect(output).toMatchFileSnapshot(`${testDirectoryPath}/output.css`);
Expand Down
5 changes: 2 additions & 3 deletions packages/core/tests/html/common/create-div/run.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { div, appendElement, insertElement } from '@sveltejs/cli-core/html';
import type { HtmlFileEditor } from '@sveltejs/cli-core';
import { div, appendElement, insertElement, type HtmlDocument } from '@sveltejs/cli-core/html';

export function run({ ast }: HtmlFileEditor<any>): void {
export function run(ast: HtmlDocument): void {
const emptyDiv = div();
insertElement(ast.childNodes, emptyDiv);
appendElement(ast.childNodes, emptyDiv);
Expand Down
5 changes: 2 additions & 3 deletions packages/core/tests/html/common/create-element/run.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { element, appendElement, insertElement } from '@sveltejs/cli-core/html';
import type { HtmlFileEditor } from '@sveltejs/cli-core';
import { element, appendElement, insertElement, type HtmlDocument } from '@sveltejs/cli-core/html';

export function run({ ast }: HtmlFileEditor<any>): void {
export function run(ast: HtmlDocument): void {
const emptySpan = element('span');
insertElement(ast.childNodes, emptySpan);
appendElement(ast.childNodes, emptySpan);
Expand Down
5 changes: 2 additions & 3 deletions packages/core/tests/html/common/from-raw/run.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { addFromRawHtml } from '@sveltejs/cli-core/html';
import type { HtmlFileEditor } from '@sveltejs/cli-core';
import { addFromRawHtml, type HtmlDocument } from '@sveltejs/cli-core/html';

export function run({ ast }: HtmlFileEditor<any>): void {
export function run(ast: HtmlDocument): void {
addFromRawHtml(ast.childNodes, '<div style="display: flex" data-foo="bar">foo</div>');
}
Loading
Loading