Skip to content

chore: update esrap #620

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

Draft
wants to merge 1 commit 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
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"dom-serializer": "^2.0.0",
"domhandler": "^5.0.3",
"domutils": "^3.2.2",
"esrap": "^1.4.9",
"esrap": "^2.0.0",
"htmlparser2": "^9.1.0",
"magic-string": "^0.30.17",
"picocolors": "^1.1.1",
Expand Down
4 changes: 2 additions & 2 deletions packages/core/tests/js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@

const inputFilePath = join(testDirectoryPath, 'input.ts');
const input = fs.existsSync(inputFilePath) ? fs.readFileSync(inputFilePath, 'utf8') : '';
const ast = parseScript(input);
const { ast, comments } = parseScript(input);

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

let output = serializeScript(ast, input);
let output = serializeScript(ast, comments, input);
if (!output.endsWith('\n')) output += '\n';
await expect(output).toMatchFileSnapshot(`${testDirectoryPath}/output.ts`);

Check failure on line 27 in packages/core/tests/js/index.ts

View workflow job for this annotation

GitHub Actions / test (macOS-latest)

tests/js/index.ts > common > jsdoc-comment

Error: Snapshot `common > jsdoc-comment 1` mismatched - Expected + Received @@ -1,8 +1,5 @@ - /** - * @param {import("$lib/paraglide/runtime").AvailableLanguageTag} newLanguage - */ function switchToLanguage(newLanguage) { const canonicalPath = i18n.route(page.url.pathname); const localisedPath = i18n.resolveRoute(canonicalPath, newLanguage); goto(localisedPath); ❯ tests/js/index.ts:27:5
});
}
});
Expand Down
64 changes: 6 additions & 58 deletions packages/core/tests/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
parseScript,
serializeScript,
guessIndentString,
guessQuoteStyle,
type AstTypes
} from '../tooling/index.ts';

Expand Down Expand Up @@ -48,57 +47,6 @@
expect(guessIndentString(code)).toBe(' ');
});

test('guessQuoteStyle - single simple', () => {
const code = dedent`
console.log('asd');
`;
const ast = parseScript(code);

expect(guessQuoteStyle(ast)).toBe('single');
});

test('guessQuoteStyle - single complex', () => {
const code = dedent`
import foo from 'bar';

console.log("bar");
const foobar = 'foo';
`;
const ast = parseScript(code);

expect(guessQuoteStyle(ast)).toBe('single');
});

test('guessQuoteStyle - double simple', () => {
const code = dedent`
console.log("asd");
`;
const ast = parseScript(code);

expect(guessQuoteStyle(ast)).toBe('double');
});

test('guessQuoteStyle - double complex', () => {
const code = dedent`
import foo from 'bar';

console.log("bar");
const foobar = "foo";
`;
const ast = parseScript(code);

expect(guessQuoteStyle(ast)).toBe('double');
});

test('guessQuoteStyle - no quotes', () => {
const code = dedent`
const foo = true;
`;
const ast = parseScript(code);

expect(guessQuoteStyle(ast)).toBe(undefined);
});

const newVariableDeclaration: AstTypes.VariableDeclaration = {
type: 'VariableDeclaration',
kind: 'const',
Expand Down Expand Up @@ -126,13 +74,13 @@
const foobar = "foo";
}
`;
const ast = parseScript(code);
const { ast, comments } = parseScript(code);
const method = ast.body[1] as AstTypes.FunctionDeclaration;

method.body.body.push(newVariableDeclaration);

// new variable is added with correct indentation and matching quotes
expect(serializeScript(ast, code)).toMatchInlineSnapshot(`
expect(serializeScript(ast, comments, code)).toMatchInlineSnapshot(`

Check failure on line 83 in packages/core/tests/utils.ts

View workflow job for this annotation

GitHub Actions / test (macOS-latest)

tests/utils.ts > integration - simple

Error: Snapshot `integration - simple 1` mismatched - Expected + Received @@ -2,7 +2,7 @@ function bar() { console.log("bar"); const foobar = "foo"; - const foobar2 = "test"; + const foobar2 = 'test'; }" ❯ tests/utils.ts:83:47
"import foo from 'bar';

function bar() {
Expand All @@ -153,13 +101,13 @@
const foobar = 'foo';
}
`;
const ast = parseScript(code);
const { ast, comments } = parseScript(code);
const method = ast.body[1] as AstTypes.FunctionDeclaration;

method.body.body.push(newVariableDeclaration);

// new variable is added with correct indentation and matching quotes
expect(serializeScript(ast, code)).toMatchInlineSnapshot(`
expect(serializeScript(ast, comments, code)).toMatchInlineSnapshot(`
"import foo from 'bar';

function bar() {
Expand All @@ -176,9 +124,9 @@
/** @type {string} */
let foo = 'bar';
`;
const ast = parseScript(code);
const { ast, comments } = parseScript(code);

expect(serializeScript(ast, code)).toMatchInlineSnapshot(`
expect(serializeScript(ast, comments, code)).toMatchInlineSnapshot(`
"/** @type {string} */
let foo = 'bar';"
`);
Expand Down
91 changes: 27 additions & 64 deletions packages/core/tooling/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
} from 'postcss';
import * as fleece from 'silver-fleece';
import { print as esrapPrint } from 'esrap';
import ts from 'esrap/languages/ts';
import * as acorn from 'acorn';
import { tsPlugin } from '@sveltejs/acorn-typescript';

Expand Down Expand Up @@ -47,19 +48,21 @@ export type {

/**
* Parses as string to an AST. Code below is taken from `esrap` to ensure compatibilty.
* https://github.com/sveltejs/esrap/blob/9daf5dd43b31f17f596aa7da91678f2650666dd0/test/common.js#L12
* https://github.com/sveltejs/esrap/blob/920491535d31484ac5fae2327c7826839d851aed/test/common.js#L14
*/
export function parseScript(content: string): TsEstree.Program {
export function parseScript(content: string): {
ast: TsEstree.Program;
comments: TsEstree.Comment[];
} {
const comments: TsEstree.Comment[] = [];

const acornTs = acorn.Parser.extend(tsPlugin());

// Acorn doesn't add comments to the AST by itself. This factory returns the capabilities to add them after the fact.
const ast = acornTs.parse(content, {
ecmaVersion: 'latest',
sourceType: 'module',
locations: true,
onComment: (block, value, start, end) => {
onComment: (block, value, start, end, startLoc, endLoc) => {
if (block && /\n/.test(value)) {
let a = start;
while (a > 0 && content[a - 1] !== '\n') a -= 1;
Expand All @@ -71,38 +74,31 @@ export function parseScript(content: string): TsEstree.Program {
value = value.replace(new RegExp(`^${indentation}`, 'gm'), '');
}

comments.push({ type: block ? 'Block' : 'Line', value, start, end });
comments.push({
type: block ? 'Block' : 'Line',
value,
start,
end,
loc: { start: startLoc as TsEstree.Position, end: endLoc as TsEstree.Position }
});
}
}) as TsEstree.Program;

Walker.walk(ast as TsEstree.Node, null, {
_(commentNode, { next }) {
let comment: TsEstree.Comment;

while (comments[0] && commentNode.start && comments[0].start! < commentNode.start) {
comment = comments.shift()!;
(commentNode.leadingComments ??= []).push(comment);
}

next();

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

if (/^[,) \t]*$/.test(slice)) {
commentNode.trailingComments = [comments.shift()!];
}
}
}
});

return ast;
return {
ast,
comments
};
}

export function serializeScript(ast: TsEstree.Node, previousContent?: string): string {
const { code } = esrapPrint(ast, {
indent: guessIndentString(previousContent),
quotes: guessQuoteStyle(ast)
export function serializeScript(
ast: TsEstree.Node,
comments: TsEstree.Comment[],
previousContent?: string
): string {
// @ts-expect-error we are still using `estree` while `esrap` is using `@typescript-eslint/types`
// which is causing these errors. But they are simmilar enough to work together.
const { code } = esrapPrint(ast, ts({ comments }), {
indent: guessIndentString(previousContent)
});
return code;
}
Expand Down Expand Up @@ -205,36 +201,3 @@ export function guessIndentString(str: string | undefined): string {
return '\t';
}
}

export function guessQuoteStyle(ast: TsEstree.Node): 'single' | 'double' | undefined {
let singleCount = 0;
let doubleCount = 0;

Walker.walk(ast, null, {
Literal(node) {
if (node.raw && node.raw.length >= 2) {
// we have at least two characters in the raw string that could represent both quotes
const quotes = [node.raw[0], node.raw[node.raw.length - 1]];
for (const quote of quotes) {
switch (quote) {
case "'":
singleCount++;
break;
case '"':
doubleCount++;
break;
default:
break;
}
}
}
}
});

if (singleCount === 0 && doubleCount === 0) {
// new file or file without any quotes
return undefined;
}

return singleCount > doubleCount ? 'single' : 'double';
}
16 changes: 8 additions & 8 deletions packages/core/tooling/js/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export function areNodesEqual(node: AstTypes.Node, otherNode: AstTypes.Node): bo

const nodeClone = stripAst(decircular(node), ['loc', 'raw']);
const otherNodeClone = stripAst(decircular(otherNode), ['loc', 'raw']);
return serializeScript(nodeClone) === serializeScript(otherNodeClone);
return serializeScript(nodeClone, []) === serializeScript(otherNodeClone, []);
}

export function createBlockStatement(): AstTypes.BlockStatement {
Expand All @@ -118,18 +118,18 @@ export function appendFromString(
node: AstTypes.BlockStatement | AstTypes.Program,
options: { code: string }
): void {
const program = parseScript(dedent(options.code));
const { ast } = parseScript(dedent(options.code));

for (const childNode of program.body) {
for (const childNode of ast.body) {
// @ts-expect-error
node.body.push(childNode);
}
}

export function parseExpression(code: string): AstTypes.Expression {
const program = parseScript(dedent(code));
stripAst(program, ['raw']);
const statement = program.body[0]!;
const { ast } = parseScript(dedent(code));
stripAst(ast, ['raw']);
const statement = ast.body[0]!;
if (statement.type !== 'ExpressionStatement') {
throw new Error('Code provided was not an expression');
}
Expand All @@ -142,8 +142,8 @@ export function parseStatement(code: string): AstTypes.Statement {
}

export function parseFromString<T extends AstTypes.Node>(code: string): T {
const program = parseScript(dedent(code));
const statement = program.body[0]!;
const { ast } = parseScript(dedent(code));
const statement = ast.body[0]!;

return statement as T;
}
Expand Down
10 changes: 6 additions & 4 deletions packages/core/tooling/parsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ type ParseBase = {
generateCode(): string;
};

export function parseScript(source: string): { ast: utils.AstTypes.Program } & ParseBase {
const ast = utils.parseScript(source);
const generateCode = () => utils.serializeScript(ast, source);
export function parseScript(
source: string
): { ast: utils.AstTypes.Program; comments: utils.AstTypes.Comment[] } & ParseBase {
const { ast, comments } = utils.parseScript(source);
const generateCode = () => utils.serializeScript(ast, comments, source);

return { ast, source, generateCode };
return { ast, comments, source, generateCode };
}

export function parseCss(source: string): { ast: utils.CssAst } & ParseBase {
Expand Down
11 changes: 9 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading