Skip to content

Commit

Permalink
[ls] Use language service
Browse files Browse the repository at this point in the history
  • Loading branch information
mantou132 committed Nov 24, 2024
1 parent 5395960 commit 2408f3b
Show file tree
Hide file tree
Showing 33 changed files with 509 additions and 585 deletions.
4 changes: 2 additions & 2 deletions crates/swc-plugin-gem/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "swc-plugin-gem",
"version": "0.1.0",
"version": "0.1.1",
"description": "swc plugin for Gem",
"keywords": [
"swc-plugin",
Expand All @@ -10,7 +10,7 @@
"main": "swc_plugin_gem.wasm",
"files": [],
"scripts": {
"install": "node -e \"require('@swc/core').transform('',{filename:'auto-import'})\"",
"install": "node -e \"require('@swc/core').transform('',{filename:'try-auto-import-dts'})\"",
"prepublishOnly": "cross-env CARGO_TARGET_DIR=target cargo build-wasi --release && cp target/wasm32-wasip1/release/swc_plugin_gem.wasm .",
"test": "cross-env RUST_LOG=info cargo watch -x test"
},
Expand Down
13 changes: 9 additions & 4 deletions crates/swc-plugin-gem/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ use swc_common::pass::Optional;
use swc_core::ecma::visit::VisitMutWith;
use swc_core::plugin::{plugin_transform, proxies::TransformPluginProgramMetadata};
use swc_ecma_ast::Program;
use visitors::import::gen_dts;
use visitors::import::gen_once_dts;
pub use visitors::import::import_transform;
pub use visitors::memo::memo_transform;
pub use visitors::minify::minify_transform;
pub use visitors::path::path_transform;

mod visitors;

Expand All @@ -18,12 +19,11 @@ struct PluginConfig {
#[serde(default)]
pub auto_import: bool,
#[serde(default)]
/// 在安装时会尝试读取 .swcrc 生成,有些项目没有 .swcrc 文件,需要在正式变异时生成
pub auto_import_dts: bool,
#[serde(default)]
pub resolve_path: bool,
#[serde(default)]
pub esm_provider: String,
#[serde(default)]
pub hmr: bool,
}

Expand All @@ -35,8 +35,9 @@ pub fn process_transform(mut program: Program, data: TransformPluginProgramMetad
let config =
serde_json::from_str::<PluginConfig>(plugin_config).expect("invalid config for gem plugin");

// 执行在每个文件
if config.auto_import_dts {
gen_dts();
gen_once_dts();
}

program.visit_mut_with(&mut (
Expand All @@ -52,6 +53,10 @@ pub fn process_transform(mut program: Program, data: TransformPluginProgramMetad
enabled: config.style_minify,
visitor: minify_transform(),
},
Optional {
enabled: config.resolve_path,
visitor: path_transform(),
},
));

program
Expand Down
10 changes: 9 additions & 1 deletion crates/swc-plugin-gem/src/visitors/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,15 @@ pub fn import_transform() -> impl VisitMut {
TransformVisitor::default()
}

pub fn gen_dts() {
static mut GEN_DTS: bool = false;

pub fn gen_once_dts() {
unsafe {
if GEN_DTS {
return;
}
GEN_DTS = true;
}
// https://github.com/swc-project/swc/discussions/4997
let types_dir = "/cwd/node_modules/@types/auto-import";
let mut import_list: Vec<String> = vec![];
Expand Down
1 change: 1 addition & 0 deletions crates/swc-plugin-gem/src/visitors/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod import;
pub mod memo;
pub mod minify;
pub mod path;
35 changes: 35 additions & 0 deletions crates/swc-plugin-gem/src/visitors/path.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
///! 验证模块是否为文件,是就添加 .js 否就添加 /index.js
///! 识别模块是否为相对路径,如何是 ts 需要处理
use swc_core::ecma::visit::{noop_visit_mut_type, VisitMut};
use swc_ecma_ast::{CallExpr, Callee, ExprOrSpread, ImportDecl, Lit, Str};

fn resolve_path(origin: &str) -> Str {
return format!("{}.js", origin).into();
}

#[derive(Default)]
struct TransformVisitor {}

impl VisitMut for TransformVisitor {
noop_visit_mut_type!();

fn visit_mut_import_decl(&mut self, node: &mut ImportDecl) {
node.src = resolve_path(node.src.value.as_str()).into();
}

// 只处理 string 的动态导入
fn visit_mut_call_expr(&mut self, node: &mut CallExpr) {
if let Callee::Import(_) = node.callee {
if let Some(Some(Lit::Str(source))) = node.args.get(0).map(|e| e.expr.as_lit()) {
node.args = vec![ExprOrSpread {
spread: None,
expr: resolve_path(source.value.as_str()).into(),
}]
}
}
}
}

pub fn path_transform() -> impl VisitMut {
TransformVisitor::default()
}
15 changes: 14 additions & 1 deletion crates/swc-plugin-gem/tests/fixture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::path::PathBuf;
use swc_core::ecma::transforms::testing::test_fixture;
use swc_ecma_parser::{Syntax, TsSyntax};
use swc_ecma_visit::visit_mut_pass;
use swc_plugin_gem::{import_transform, memo_transform, minify_transform};
use swc_plugin_gem::{import_transform, memo_transform, minify_transform, path_transform};
use testing::fixture;

fn get_syntax() -> Syntax {
Expand Down Expand Up @@ -51,3 +51,16 @@ fn fixture_minify(input: PathBuf) {
Default::default(),
);
}

#[fixture("tests/fixture/path/input.ts")]
fn fixture_path(input: PathBuf) {
let output = input.parent().unwrap().join("output.ts");

test_fixture(
get_syntax(),
&|_| visit_mut_pass(path_transform()),
&input,
&output,
Default::default(),
);
}
3 changes: 3 additions & 0 deletions crates/swc-plugin-gem/tests/fixture/path/input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// @ts-nocheck
import '@mantou/gem';
import '@mantou/gem/helper/react-shim';
3 changes: 3 additions & 0 deletions crates/swc-plugin-gem/tests/fixture/path/output.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// @ts-nocheck
import '@mantou/gem/index.js';
import '@mantou/gem/helper/react-shim.js';
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"test": "lerna exec --concurrency=1 --scope \"{@mantou/gem,gem-book,duoyun-ui}\" -- pnpm test",
"release": "lerna version",
"prepare:build": "pnpm -C packages/gem build && lerna exec --scope \"{gem-analyzer,duoyun-ui,gem-book,gem-port}\" -- pnpm build",
"prepare:link": "lerna exec --scope \"{gem-book,@mantou/gem-port}\" -- npm link",
"prepare:link": "lerna exec --scope \"{gem-book,@mantou/gem-port}\" -- pnpm link -g",
"prepare": "husky install && pnpm prepare:build && pnpm prepare:link"
},
"devDependencies": {
Expand Down
6 changes: 3 additions & 3 deletions packages/duoyun-ui/src/lib/encode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export function b64ToUtf8(str: string) {
}

export function base64ToArrayBuffer(str: string) {
return new Uint8Array([...self.atob(safeUrlToBase64Str(str))].map((char) => char.charCodeAt(0))).buffer;
return new Uint8Array([...atob(safeUrlToBase64Str(str))].map((char) => char.charCodeAt(0))).buffer;
}

function base64ToSafeUrl(str: string) {
Expand All @@ -25,15 +25,15 @@ function base64ToSafeUrl(str: string) {

/**Converted string to Base64, `isSafe` indicates URL safe */
export function utf8ToB64(str: string, isSafe?: boolean) {
const base64 = self.btoa(
const base64 = btoa(
encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (_, p1) => String.fromCharCode(Number(`0x${p1}`))),
);
return isSafe ? base64ToSafeUrl(base64) : base64;
}

// https://github.com/tc39/proposal-arraybuffer-base64
export function arrayBufferToBase64(arrayBuffer: ArrayBuffer, isSafe?: boolean) {
const base64 = self.btoa(String.fromCharCode(...new Uint8Array(arrayBuffer)));
const base64 = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer)));
return isSafe ? base64ToSafeUrl(base64) : base64;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/duoyun-ui/src/lib/image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function createCanvas(width?: number, height?: number) {
}

export function createDataURLFromSVG(rawStr: string) {
return `data:image/svg+xml;base64,${self.btoa(rawStr)}`;
return `data:image/svg+xml;base64,${btoa(rawStr)}`;
}

// if `bg` is't `HexColor`, text fill color error
Expand Down
12 changes: 7 additions & 5 deletions packages/duoyun-ui/src/lib/timer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { logger } from '@mantou/gem/helper/logger';

const setTimeout = globalThis.setTimeout as typeof self.setTimeout;

/**Until the callback function resolve */
export async function forever<T>(fn: () => Promise<T>, interval = 1000): Promise<T> {
try {
Expand All @@ -21,7 +23,7 @@ export function polling(fn: (args?: any[]) => any, delay: number) {
} catch {
} finally {
if (!hasExit) {
timer = self.setTimeout(poll, delay);
timer = setTimeout(poll, delay);
}
}
};
Expand All @@ -48,7 +50,7 @@ export function throttle<T extends (...args: any) => any>(
let timer = 0;
let first = 0;
const exec = (...rest: Parameters<T>) => {
timer = self.setTimeout(() => (timer = 0), wait);
timer = setTimeout(() => (timer = 0), wait);
fn(...(rest as any));
};
return (...rest: Parameters<T>) => {
Expand All @@ -62,7 +64,7 @@ export function throttle<T extends (...args: any) => any>(
exec(...rest);
} else {
clearTimeout(timer);
timer = self.setTimeout(() => exec(...rest), wait);
timer = setTimeout(() => exec(...rest), wait);
}
};
}
Expand All @@ -76,9 +78,9 @@ export function debounce<T extends (...args: any) => any>(
return function (...args: Parameters<T>) {
return new Promise<Awaited<ReturnType<typeof fn>>>((resolve, reject) => {
clearTimeout(timer);
timer = self.setTimeout(
timer = setTimeout(
() => {
timer = self.setTimeout(() => (timer = 0), wait);
timer = setTimeout(() => (timer = 0), wait);
Promise.resolve(fn(...(args as any)))
.then(resolve)
.catch(reject);
Expand Down
2 changes: 1 addition & 1 deletion packages/duoyun-ui/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ export function createCacheStore<T extends Record<string, any>>(
);
};

self.addEventListener('pagehide', saveStore);
addEventListener('pagehide', saveStore);

return { store, saveStore };
}
Expand Down
16 changes: 11 additions & 5 deletions packages/language-service/package.json
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
{
"name": "vscode-gem-languageservice",
"version": "0.0.1",
"version": "0.0.4",
"description": "Language service for Gem",
"keywords": [
"gem",
"language-service"
],
"type": "module",
"module": "src/index.ts",
"bin": "src/index.ts",
"main": "src/index.ts",
"bin": "dist/index.js",
"files": [
"/src/"
"/dist/"
],
"scripts": {},
"scripts": {
"prepublishOnly": "esbuild ./src/index.ts --outdir=./dist --platform=node --sourcemap --bundle --packages=external"
},
"dependencies": {
"@vscode/emmet-helper": "^2.9.3",
"duoyun-ui": "^2.2.0",
"gem-analyzer": "^2.2.0",
"ts-morph": "^13.0.0",
"typescript": "^5.6.2",
"vscode-css-languageservice": "^6.3.1",
"vscode-html-languageservice": "^5.3.1",
"vscode-languageserver": "^9.0.1",
"vscode-languageserver-textdocument": "^1.0.12"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { CompletionList, TextDocument, Position } from 'vscode';
import type { CompletionList } from 'vscode-languageserver';
import type { Position, TextDocument } from 'vscode-languageserver-textdocument';

export class CompletionsCache {
#cachedCompletionsFile?: string;
Expand All @@ -13,7 +14,7 @@ export class CompletionsCache {
getCached(doc: TextDocument, position: Position) {
if (
this.#completions &&
doc.fileName === this.#cachedCompletionsFile &&
doc.uri === this.#cachedCompletionsFile &&
this.#equalPositions(position, this.#cachedCompletionsPosition) &&
doc.getText() === this.#cachedCompletionsContent
) {
Expand All @@ -24,7 +25,7 @@ export class CompletionsCache {
}

updateCached(context: TextDocument, position: Position, completions: CompletionList) {
this.#cachedCompletionsFile = context.fileName;
this.#cachedCompletionsFile = context.uri;
this.#cachedCompletionsPosition = position;
this.#cachedCompletionsContent = context.getText();
this.#completions = completions;
Expand Down
31 changes: 31 additions & 0 deletions packages/language-service/src/color.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { rgbToHexColor, parseHexColor } from 'duoyun-ui/lib/color';
import { Range, Color } from 'vscode-languageserver/node';
import type { ColorInformation, ColorPresentation } from 'vscode-languageserver/node';
import type { HexColor } from 'duoyun-ui/lib/color';
import type { TextDocument } from 'vscode-languageserver-textdocument';

import { COLOR_REG } from './constants';

export class ColorProvider {
provideDocumentColors(document: TextDocument) {
COLOR_REG.exec('null');

const documentText = document.getText();
const colors: ColorInformation[] = [];

let match: RegExpExecArray | null;
while ((match = COLOR_REG.exec(documentText)) !== null) {
const hex = match.groups!.content as HexColor;
const [red, green, blue, alpha] = parseHexColor(hex);
const offset = match.index + (match.groups!.start?.length || 0);
const range = Range.create(document.positionAt(offset), document.positionAt(offset + hex.length));
const color = Color.create(red / 255, green / 255, blue / 255, alpha);
colors.push({ range, color });
}
return colors;
}

provideColorPresentations({ red, green, blue, alpha }: Color): ColorPresentation[] {
return [{ label: rgbToHexColor([red * 255, green * 255, blue * 255, alpha]) }];
}
}
13 changes: 13 additions & 0 deletions packages/language-service/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export const COLOR_REG = /(?<start>'|")?(?<content>#([0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{3,4}))($1|\s*;|\s*\))/g;

// 直接通过正则匹配 css 片段,通过条件的结束 ` 号匹配
export const CSS_REG = /(?<start>\/\*\s*css\s*\*\/\s*`|(?<!`)(?:css|less|scss)\s*`)(?<content>.*?)(`(?=;|,?\s*\)))/gs;
// 直接通过正则匹配 style 片段,通过条件的结束 ` 号匹配
// 语言服务和高亮都只支持 styled 写法
export const STYLE_REG = /(?<start>\/\*\s*style\s*\*\/\s*`|(?<!`)styled?\s*`)(?<content>.*?)(`(?=,|\s*}\s*\)))/gs;

// 处理后进行正则匹配,所以不需要验证后面的 ` 号
export const HTML_REG = /(?<start>\/\*\s*html\s*\*\/\s*`|(?<!`)(?:html|raw)\s*`)(?<content>[^`]*)(`)/g;

// 展位字符串模版中的插槽
export const SLOT_TOKEN = '_';
Loading

0 comments on commit 2408f3b

Please sign in to comment.