Skip to content

Commit 51cb49e

Browse files
committed
feat: generateUseItem
1 parent 8df88a5 commit 51cb49e

File tree

3 files changed

+221
-2
lines changed

3 files changed

+221
-2
lines changed

packages/language-server/src/codegen.ts

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ export function* generateStatement(node: Parser.SyntaxNode): Generator<Code> {
2121
yield* generateExpression(node.namedChildren[0]);
2222
yield ";";
2323
break;
24+
case "use_declaration":
25+
yield* generateUse(node);
26+
break;
2427
default:
2528
yield* generateExpression(node);
2629
}
@@ -271,3 +274,193 @@ function* generateSelf(node: Parser.SyntaxNode): Generator<Code> {
271274
`this`,
272275
);
273276
}
277+
278+
function* generateUse(node: Parser.SyntaxNode): Generator<Code> {
279+
const reexportsNamed: Code[] = [];
280+
const reexportsAll: string[] = [];
281+
yield* generateUseItem(node.childForFieldName("argument")!);
282+
if (node.namedChildren[0]?.type === "visibility_modifier") {
283+
if (reexportsNamed.length) {
284+
yield "export {";
285+
yield* reexportsNamed;
286+
yield "};\n";
287+
}
288+
if (reexportsAll.length) {
289+
for (const path of reexportsAll) {
290+
yield `export * from "`;
291+
yield path;
292+
yield `";\n`;
293+
}
294+
}
295+
}
296+
else {
297+
if (reexportsAll.length) {
298+
throw new Error("Wildcard import is not supported");
299+
}
300+
}
301+
302+
function* generateUseItem(node: Parser.SyntaxNode, base = "", alias?: Code): Generator<Code> {
303+
switch (node.type) {
304+
case "scoped_use_list": {
305+
const path = getPath(node.namedChildren[0]);
306+
let wildcard: Code[] | undefined;
307+
const named: [original: Code, alias?: Code][] = [];
308+
for (const child of node.namedChildren[1].namedChildren) {
309+
if (child.type === "self") {
310+
wildcard = [...wrapWith(
311+
child.startIndex,
312+
child.endIndex,
313+
codeFeatures.verification,
314+
getSelfName(node.namedChildren[0]),
315+
)];
316+
}
317+
else if (child.type === "identifier") {
318+
named.push([[child.text, child.startIndex]]);
319+
}
320+
else if (child.type === "use_as_clause") {
321+
const original = child.namedChildren[0];
322+
const alias = child.namedChildren[1];
323+
if (original.type === "self") {
324+
wildcard = [[alias.text, alias.startIndex]];
325+
}
326+
else if (original.type === "identifier") {
327+
named.push([[original.text, original.startIndex], [alias.text, alias.startIndex]]);
328+
}
329+
else {
330+
yield* generateUseItem(original, path, [alias.text, alias.startIndex]);
331+
}
332+
}
333+
else {
334+
yield* generateUseItem(child, path);
335+
}
336+
}
337+
if (wildcard) {
338+
reexportsNamed.push(...wildcard, ", ");
339+
yield `import * as `;
340+
yield* wildcard;
341+
yield ` from `;
342+
yield* generatePath(node, path);
343+
yield `;\n`;
344+
}
345+
if (named.length > 0) {
346+
yield "import { ";
347+
let isFirst = true;
348+
for (const [name, alias] of named) {
349+
if (!isFirst)
350+
yield `, `;
351+
isFirst = false;
352+
353+
reexportsNamed.push(alias || name, ", ");
354+
yield name;
355+
if (alias) {
356+
yield ` as `;
357+
yield alias;
358+
}
359+
}
360+
yield ` } from `;
361+
yield* generatePath(node, path);
362+
yield `;\n`;
363+
}
364+
break;
365+
}
366+
case "scoped_identifier": {
367+
const path = getPath(node.namedChildren[0]);
368+
const name = node.namedChildren[1];
369+
if (name.text === "self") {
370+
const nameCode = alias
371+
? [alias]
372+
: [...wrapWith(
373+
name.startIndex,
374+
name.endIndex,
375+
codeFeatures.verification,
376+
getSelfName(node.namedChildren[0]),
377+
)];
378+
reexportsNamed.push(...nameCode, ", ");
379+
yield `import * as `;
380+
yield* nameCode;
381+
yield ` from `;
382+
yield* generatePath(node, path);
383+
yield `;\n`;
384+
}
385+
else {
386+
reexportsNamed.push(alias || [name.text, name.startIndex], ", ");
387+
yield `import { `;
388+
yield [name.text, name.startIndex];
389+
if (alias) {
390+
yield ` as `;
391+
yield alias;
392+
}
393+
yield ` } from `;
394+
yield* generatePath(node, path);
395+
yield `;\n`;
396+
}
397+
break;
398+
}
399+
case "identifier": {
400+
yield `import * as `;
401+
yield [node.text, node.startIndex];
402+
yield ` from `;
403+
yield* generatePath(node, node.text);
404+
yield `;\n`;
405+
break;
406+
}
407+
case "use_as_clause": {
408+
const original = node.namedChildren[0];
409+
yield* generateUseItem(original, base, alias);
410+
break;
411+
}
412+
case "use_wildcard": {
413+
const path = getPath(node.namedChildren[0]);
414+
reexportsAll.push(path);
415+
break;
416+
}
417+
default:
418+
throw new Error(`Not implemented: ${node.type}`);
419+
}
420+
421+
function getPath(path: Parser.SyntaxNode): string {
422+
return base ? `${base}/${getPathImpl(path)}` : getPathImpl(path);
423+
}
424+
425+
function getPathImpl(path: Parser.SyntaxNode): string {
426+
if (path.type === "identifier") {
427+
return `${path.text}`;
428+
}
429+
else if (path.type === "scoped_identifier") {
430+
return `${getPathImpl(path.namedChildren[0])}/${path.namedChildren[1].text}`;
431+
}
432+
else if (path.type === "crate") {
433+
return `@`;
434+
}
435+
else {
436+
throw new Error(`Not implemented: ${path.type}`);
437+
}
438+
}
439+
440+
function getSelfName(path: Parser.SyntaxNode): string {
441+
if (path.type === "identifier") {
442+
return path.text;
443+
}
444+
else if (path.type === "scoped_identifier") {
445+
return getSelfName(path.namedChildren[1]);
446+
}
447+
else if (path.type === "crate") {
448+
return "crate";
449+
}
450+
else {
451+
throw new Error(`Not implemented: ${path.type}`);
452+
}
453+
}
454+
}
455+
456+
function generatePath(node: Parser.SyntaxNode, path: string): Generator<Code> {
457+
return wrapWith(
458+
node.startIndex,
459+
node.endIndex,
460+
codeFeatures.verification,
461+
`"`,
462+
path,
463+
`"`,
464+
);
465+
}
466+
}

packages/language-server/src/utils/index.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,36 @@
11
import type { CodeInformation } from "@volar/language-core";
22
import type { Code } from "../types";
33

4-
export function* wrapWith(
4+
export function wrapWith(
55
start: number,
66
end: number,
77
features: CodeInformation,
88
...codes: Code[]
9+
): Generator<Code>;
10+
export function wrapWith(
11+
start: number,
12+
end: number,
13+
features: CodeInformation,
14+
codes: () => Iterable<Code>
15+
): Generator<Code>;
16+
export function* wrapWith(
17+
start: number,
18+
end: number,
19+
features: CodeInformation,
20+
...codes: Code[] | [() => Iterable<Code>]
921
): Generator<Code> {
1022
yield [``, start, features];
1123
let offset = 1;
12-
for (const code of codes) {
24+
25+
let normalized: Iterable<Code>;
26+
if (codes.length === 1 && typeof codes[0] === "function") {
27+
normalized = codes[0]();
28+
}
29+
else {
30+
normalized = codes as Code[];
31+
}
32+
33+
for (const code of normalized) {
1334
if (typeof code !== "string") {
1435
offset++;
1536
}

playground/basic.jsrs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
// pub use crate::x::{x::y::{self, y1, y2}, z::self as t, t as h};
2+
// pub use t::x;
3+
// pub use s::s;
4+
// pub use r;
5+
16
1 + 1;
27
let mut a = 1;
38
a.toString;

0 commit comments

Comments
 (0)