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: explore parsers and printers #380

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
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/addons/drizzle/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,8 @@ export default defineAddon({
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/addons/eslint/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,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 @@ -55,7 +54,7 @@ export default defineAddon({
const { ast, generateCode } = parseScript(content);

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

const gitIgnorePathStatement = common.statementFromString(
Expand Down
17 changes: 9 additions & 8 deletions packages/addons/lucia/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,18 @@
sv.dependency('@node-rs/argon2', '^1.1.0');
}

sv.file(`drizzle.config.${ext}`, (content) => {

Check failure on line 55 in packages/addons/lucia/index.ts

View workflow job for this annotation

GitHub Actions / test (macOS-latest)

_tests/all-addons/test.ts > run all addons - kit-js

Error: Unable to process 'drizzle.config.js'. Reason: Failed to detect DB dialect in your `drizzle.config.[js|ts]` file ❯ Object.run lucia/index.ts:55:6 ❯ Object.ctx.run _tests/_setup/suite.ts:57:4 ❯ _tests/all-addons/test.ts:23:14

Check failure on line 55 in packages/addons/lucia/index.ts

View workflow job for this annotation

GitHub Actions / test (macOS-latest)

_tests/all-addons/test.ts > run all addons - kit-js

Error: Unable to process 'drizzle.config.js'. Reason: Failed to detect DB dialect in your `drizzle.config.[js|ts]` file ❯ Object.run lucia/index.ts:55:6 ❯ Object.ctx.run _tests/_setup/suite.ts:57:4 ❯ _tests/all-addons/test.ts:23:14

Check failure on line 55 in packages/addons/lucia/index.ts

View workflow job for this annotation

GitHub Actions / test (macOS-latest)

_tests/all-addons/test.ts > run all addons - kit-js

Error: Unable to process 'drizzle.config.js'. Reason: Failed to detect DB dialect in your `drizzle.config.[js|ts]` file ❯ Object.run lucia/index.ts:55:6 ❯ Object.ctx.run _tests/_setup/suite.ts:57:4 ❯ _tests/all-addons/test.ts:23:14

Check failure on line 55 in packages/addons/lucia/index.ts

View workflow job for this annotation

GitHub Actions / test (macOS-latest)

_tests/all-addons/test.ts > run all addons - kit-js

Error: Unable to process 'drizzle.config.js'. Reason: Failed to detect DB dialect in your `drizzle.config.[js|ts]` file ❯ Object.run lucia/index.ts:55:6 ❯ Object.ctx.run _tests/_setup/suite.ts:57:4 ❯ _tests/all-addons/test.ts:23:14

Check failure on line 55 in packages/addons/lucia/index.ts

View workflow job for this annotation

GitHub Actions / test (macOS-latest)

_tests/all-addons/test.ts > run all addons - kit-ts

Error: Unable to process 'drizzle.config.ts'. Reason: Failed to detect DB dialect in your `drizzle.config.[js|ts]` file ❯ Object.run lucia/index.ts:55:6 ❯ Object.ctx.run _tests/_setup/suite.ts:57:4 ❯ _tests/all-addons/test.ts:23:14

Check failure on line 55 in packages/addons/lucia/index.ts

View workflow job for this annotation

GitHub Actions / test (macOS-latest)

_tests/all-addons/test.ts > run all addons - kit-ts

Error: Unable to process 'drizzle.config.ts'. Reason: Failed to detect DB dialect in your `drizzle.config.[js|ts]` file ❯ Object.run lucia/index.ts:55:6 ❯ Object.ctx.run _tests/_setup/suite.ts:57:4 ❯ _tests/all-addons/test.ts:23:14

Check failure on line 55 in packages/addons/lucia/index.ts

View workflow job for this annotation

GitHub Actions / test (macOS-latest)

_tests/all-addons/test.ts > run all addons - kit-ts

Error: Unable to process 'drizzle.config.ts'. Reason: Failed to detect DB dialect in your `drizzle.config.[js|ts]` file ❯ Object.run lucia/index.ts:55:6 ❯ Object.ctx.run _tests/_setup/suite.ts:57:4 ❯ _tests/all-addons/test.ts:23:14

Check failure on line 55 in packages/addons/lucia/index.ts

View workflow job for this annotation

GitHub Actions / test (macOS-latest)

_tests/all-addons/test.ts > run all addons - kit-ts

Error: Unable to process 'drizzle.config.ts'. Reason: Failed to detect DB dialect in your `drizzle.config.[js|ts]` file ❯ Object.run lucia/index.ts:55:6 ❯ Object.ctx.run _tests/_setup/suite.ts:57:4 ❯ _tests/all-addons/test.ts:23:14

Check failure on line 55 in packages/addons/lucia/index.ts

View workflow job for this annotation

GitHub Actions / test (macOS-latest)

_tests/lucia/test.ts > core - kit-js

Error: Unable to process 'drizzle.config.js'. Reason: Failed to detect DB dialect in your `drizzle.config.[js|ts]` file ❯ Object.run lucia/index.ts:55:6 ❯ Object.ctx.run _tests/_setup/suite.ts:57:4 ❯ _tests/lucia/test.ts:10:14

Check failure on line 55 in packages/addons/lucia/index.ts

View workflow job for this annotation

GitHub Actions / test (macOS-latest)

_tests/lucia/test.ts > core - kit-js

Error: Unable to process 'drizzle.config.js'. Reason: Failed to detect DB dialect in your `drizzle.config.[js|ts]` file ❯ Object.run lucia/index.ts:55:6 ❯ Object.ctx.run _tests/_setup/suite.ts:57:4 ❯ _tests/lucia/test.ts:10:14
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' && node.value.value === typeof 'string') {
drizzleDialect = node.value.value as Dialect;
}
if (isProp('schema', node) && node.value.type === 'StringLiteral') {
if (isProp('schema', node) && node.value.type === 'Literal' && node.value.value === typeof 'string') {
schemaPath = node.value.value;
}
}
Expand Down Expand Up @@ -600,13 +600,14 @@
type: 'Identifier',
name
},
computed: false,
typeAnnotation: {
type: 'TSTypeAnnotation',
typeAnnotation: {
type: 'TSIndexedAccessType',
objectType: {
type: 'TSImportType',
argument: { type: 'StringLiteral', value: '$lib/server/auth' },
argument: { type: 'Literal', value: '$lib/server/auth' },
qualifier: {
type: 'Identifier',
name: 'SessionValidationResult'
Expand All @@ -615,7 +616,7 @@
indexType: {
type: 'TSLiteralType',
literal: {
type: 'StringLiteral',
type: 'Literal',
value: name
}
}
Expand Down Expand Up @@ -648,7 +649,7 @@
};`;
}

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

// prettier-ignore
Expand Down
4 changes: 2 additions & 2 deletions packages/addons/sveltekit-adapter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ export default defineAddon({

const { value: config } = exports.defaultExport(ast, object.createEmpty());
const kitConfig = config.properties.find(
(p) => p.type === 'ObjectProperty' && p.key.type === 'Identifier' && p.key.name === 'kit'
) as AstTypes.ObjectProperty | undefined;
(p) => p.type === 'Property' && p.key.type === 'Identifier' && p.key.name === 'kit'
) as AstTypes.Property | undefined;

if (kitConfig && kitConfig.value.type === 'ObjectExpression') {
// only overrides the `adapter` property so we can reset it's args
Expand Down
14 changes: 11 additions & 3 deletions packages/addons/vitest-addon/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ export default defineAddon({
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 @@ -62,7 +64,10 @@ export default defineAddon({
} else {
// otherwise, just remove the `defineConfig` specifier
const idxToRemove = defineConfigImportDecl?.specifiers?.findIndex(
(s) => s.type === 'ImportSpecifier' && s.imported.name === 'defineConfig'
(s) =>
s.type === 'ImportSpecifier' &&
s.imported.type == 'Identifier' &&
s.imported.name === 'defineConfig'
);
if (idxToRemove) defineConfigImportDecl?.specifiers?.splice(idxToRemove, 1);
}
Expand All @@ -81,7 +86,10 @@ export default defineAddon({
) {
// 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'
(sp) =>
sp.type === 'ImportSpecifier' &&
sp.imported.type == 'Identifier' &&
sp.imported.name === 'defineConfig'
);
const defineConfigAlias = (importSpecifier?.local?.name ?? 'defineConfig') as string;
imports.addNamed(ast, 'vitest/config', { defineConfig: defineConfigAlias });
Expand Down
114 changes: 60 additions & 54 deletions packages/ast-tooling/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { parse as tsParse } from 'recast/parsers/typescript.js';
import { parse as recastParse, print as recastPrint, type Options as RecastOptions } from 'recast';
import { Document, Element, type ChildNode } from 'domhandler';
import { ElementType, parseDocument } from 'htmlparser2';
import { removeElement, textContent } from 'domutils';
import serializeDom from 'dom-serializer';
import {
Root as CssAst,
Expand All @@ -15,8 +12,12 @@ 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';
// todo: why is this file only generated during `dev` startup, if it's prefixed with type?
// @ts-expect-error
import { TsEstree } from './ts-estree.ts';
import { print as esrapPrint } from 'esrap';
import * as acorn from 'acorn';
import { tsPlugin } from 'acorn-typescript';

/**
* Most of the AST tooling is pretty big in bundle size and bundling takes forever.
Expand Down Expand Up @@ -48,34 +49,69 @@ 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;
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 });
}
});

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

export function serializeScript(ast: AstTypes.ASTNode, previousContent?: string): string {
let options: RecastOptions | undefined;
if (!previousContent) {
// provide sensible defaults if we generate a new file
options = {
quote: 'single',
useTabs: true
};
}
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);

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

return ast as TsEstree.Program;
}

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

export function parseCss(content: string): CssAst {
Expand Down Expand Up @@ -117,41 +153,11 @@ export function stripAst<T>(node: T, propToRemove: string): T {
}

export type SvelteAst = {
jsAst: AstTypes.Program;
jsAst: TsEstree.Program;
htmlAst: Document;
cssAst: CssAst;
};

export function parseSvelte(content: string): SvelteAst {
const htmlAst = parseHtml(content);

let scriptTag, styleTag;
for (const node of htmlAst.childNodes) {
if (node.type === ElementType.Script) {
scriptTag = node;
removeElement(scriptTag);
} else if (node.type === ElementType.Style) {
styleTag = node;
removeElement(styleTag);
}
}

if (!scriptTag) {
scriptTag = new Element('script', {}, undefined, ElementType.ElementType.Script);
}
if (!styleTag) {
styleTag = new Element('style', {}, undefined, ElementType.ElementType.Style);
}

const css = textContent(styleTag);
const cssAst = parseCss(css);

const scriptValue = textContent(scriptTag);
const jsAst = parseScript(scriptValue);

return { jsAst, htmlAst, cssAst };
}

export function parseJson(content: string): any {
// some of the files we need to process contain comments. The default
// node JSON.parse fails parsing those comments.
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 @@ -24,14 +24,15 @@
}
},
"devDependencies": {
"@babel/parser": "^7.26.3",
"ast-types": "^0.16.1",
"@types/estree": "^1.0.6",
"acorn": "^8.14.0",
"acorn-typescript": "^1.4.13",
"dom-serializer": "^2.0.0",
"domhandler": "^5.0.3",
"domutils": "^3.1.0",
"esrap": "^1.3.2",
"htmlparser2": "^9.1.0",
"postcss": "^8.4.49",
"recast": "^0.23.9",
"silver-fleece": "^1.2.1",
"zimmerframe": "^1.1.2"
}
Expand Down
94 changes: 94 additions & 0 deletions packages/ast-tooling/ts-estree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import type * as estree from 'estree';

declare module 'estree' {
// new types
interface TSTypeAnnotation {
type: 'TSTypeAnnotation';
typeAnnotation: TSStringKeyword | TSTypeReference | TSUnionType | TSIndexedAccessType;
}
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;
}
interface TSIndexedAccessType {
type: 'TSIndexedAccessType';
objectType: TSImportType;
indexType: TSLiteralType;
}
interface TSLiteralType {
type: 'TSLiteralType';
literal: Literal;
}
interface TSSatisfiesExpression extends BaseNode {
type: 'TSSatisfiesExpression';
expression: Expression;
typeAnnotation: TSTypeAnnotation['typeAnnotation'];
}

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

export type { estree as TsEstree };
Loading
Loading