From f96b6b18c92e5dbbd88b41efe1322ef1c75ec2ad Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Thu, 13 Jun 2024 03:02:32 -0400 Subject: [PATCH] build(website): use typescript (#3653) Co-authored-by: Boshen --- website/package.json | 6 +- website/playground/{editor.js => editor.ts} | 18 +++- website/playground/index.html | 2 +- website/playground/{index.js => index.ts} | 92 +++++++++++-------- website/playground/symbols.js | 50 ---------- website/playground/symbols.ts | 64 +++++++++++++ .../{traverseJson.js => traverseJson.ts} | 29 +++--- website/pnpm-lock.yaml | 45 ++++++++- website/tsconfig.json | 13 +++ 9 files changed, 210 insertions(+), 109 deletions(-) rename website/playground/{editor.js => editor.ts} (58%) rename website/playground/{index.js => index.ts} (90%) delete mode 100644 website/playground/symbols.js create mode 100644 website/playground/symbols.ts rename website/playground/{traverseJson.js => traverseJson.ts} (62%) create mode 100644 website/tsconfig.json diff --git a/website/package.json b/website/package.json index 2e2cf6a078d4..844d01503a31 100644 --- a/website/package.json +++ b/website/package.json @@ -7,7 +7,8 @@ "dev": "pnpm run wasm-dev && concurrently 'vite' 'cd .. && cargo watch --workdir website -s \"pnpm run wasm-dev\"'", "wasm-dev": "wasm-pack build --out-dir ../../npm/oxc-wasm --target web --dev --scope oxc ../crates/oxc_wasm", "build": "pnpm run wasm-build && vite build --base=https://oxc-project.github.io/oxc/", - "wasm-build": "wasm-pack build --out-dir ../../npm/oxc-wasm --target web --release --scope oxc ../crates/oxc_wasm" + "wasm-build": "wasm-pack build --out-dir ../../npm/oxc-wasm --target web --release --scope oxc ../crates/oxc_wasm", + "lint": "oxlint" }, "dependencies": { "@codemirror/autocomplete": "^6.16.2", @@ -27,7 +28,10 @@ "lzma": "^2.3.2" }, "devDependencies": { + "@types/lodash.throttle": "^4.1.9", + "@types/lzma": "^2.3.0", "@lezer/common": "^1.2.1", + "oxlint": "link:../apps/oxlint", "@oxc/oxc_wasm": "link:../npm/oxc-wasm", "concurrently": "^8.2.2", "vite": "^5.2.13", diff --git a/website/playground/editor.js b/website/playground/editor.ts similarity index 58% rename from website/playground/editor.js rename to website/playground/editor.ts index 333b8aea2c3d..c8d5331dc3a4 100644 --- a/website/playground/editor.js +++ b/website/playground/editor.ts @@ -1,6 +1,14 @@ -// Go down and find the `start` and `end` keys -export function getStartAndEnd(view, cursor) { - let start, end; +import type { TreeCursor } from "@lezer/common"; +import type { EditorView } from "codemirror"; + +/** + * Go down and find the `start` and `end` keys + */ +export function getStartAndEnd( + view: EditorView, + cursor: TreeCursor +): [start: number | undefined, end: number | undefined] { + let start: number | undefined, end: number | undefined; while (true) { if ( !start && @@ -27,6 +35,6 @@ export function getStartAndEnd(view, cursor) { return [start, end] } -export const convertToUtf8 = (sourceTextUtf8, d) => { +export const convertToUtf8 = (sourceTextUtf8: ArrayBuffer, d: number) => { return new TextDecoder().decode(sourceTextUtf8.slice(0, d)).length; -} \ No newline at end of file +} diff --git a/website/playground/index.html b/website/playground/index.html index af4850c3eb65..26e9a2e0b422 100644 --- a/website/playground/index.html +++ b/website/playground/index.html @@ -35,7 +35,7 @@ - +
Loading Wasm (~400kB)...
diff --git a/website/playground/index.js b/website/playground/index.ts similarity index 90% rename from website/playground/index.js rename to website/playground/index.ts index 772bc707933f..f2c62a7db59f 100644 --- a/website/playground/index.js +++ b/website/playground/index.ts @@ -7,16 +7,17 @@ import { EditorSelection, Compartment, RangeSet, + type Range, } from "@codemirror/state"; -import { convertToUtf8, getStartAndEnd } from './editor.js' -import { findMostInnerNodeForPosition } from './traverseJson.js' +import { convertToUtf8, getStartAndEnd } from './editor.ts' +import { findMostInnerNodeForPosition } from './traverseJson.ts' import { parser } from '@lezer/json' import { javascript, javascriptLanguage } from "@codemirror/lang-javascript"; import { rust, rustLanguage } from "@codemirror/lang-rust"; import { json, jsonLanguage } from "@codemirror/lang-json"; import { vscodeKeymap } from "@replit/codemirror-vscode-keymap"; import { githubDark } from "@ddietr/codemirror-themes/github-dark"; -import { linter, lintGutter } from "@codemirror/lint"; +import { type Diagnostic, linter, lintGutter } from "@codemirror/lint"; import { language, syntaxTree } from "@codemirror/language"; import { autocompletion } from "@codemirror/autocomplete"; import { indentWithTab, deleteLine } from "@codemirror/commands"; @@ -39,7 +40,7 @@ import { getSymbolAndReferencesSpan, renderSymbols } from "./symbols.js"; const placeholderText = ` import React, { useEffect, useRef } from 'react' -const DummyComponent:React.FC = () => { +const DummyComponent: React.FC = () => { const ref = useRef(null) useEffect(() => { @@ -47,10 +48,10 @@ const DummyComponent:React.FC = () => { }, []) return ( -
{Boolean(ref.current) ?? ( - - )} -
+
{Boolean(ref.current) ?? ( + + )} +
) } @@ -60,7 +61,7 @@ export default DummyComponent const STORAGE_KEY_CODE = "playground.code"; const ACTIVE_TAB_STORAGE_KEY_CODE = "playground.activeTab"; -const getStringFromStorage = (whatToGet) => { +const getStringFromStorage = (whatToGet: string) => { try { return localStorage.getItem(whatToGet); } catch (_e) { @@ -68,7 +69,7 @@ const getStringFromStorage = (whatToGet) => { } }; -const setStringToStorage = (whatToSet, value) => { +const setStringToStorage = (whatToSet: string, value: string) => { try { localStorage.setItem(whatToSet, value); } catch (_e) { @@ -77,7 +78,7 @@ const setStringToStorage = (whatToSet, value) => { }; const tabBtnsBindClick = (callback) => { - const buttons = document.querySelectorAll('.header.controls button'); + const buttons = document.querySelectorAll('.header.controls button'); buttons.forEach(btn => { btn.onclick = (e) => { callback(e?.target?.id); @@ -87,7 +88,7 @@ const tabBtnsBindClick = (callback) => { const switchActiveTab = (tab) => { const buttons = document.querySelectorAll('.header.controls button'); - let targetBtn = null; + let targetBtn: Element | null = null; buttons.forEach(btn => { btn.classList.remove('active') if (tab === btn.id) { @@ -109,22 +110,34 @@ const initActiveTab = () => { btn.classList.add('active'); } +type EditorViewKind = + | 'ast' + | 'codegen' + | 'format' + | 'ir' + | 'minify' + | 'prettier-ir' + | 'prettier' + | 'scope' + | 'symbol' + class Playground { - oxc; + oxc: Oxc; sourceTextUtf8 // source text in Uint8Array, for converting from utf8 to utf16 span - runOptions; - parserOptions; - codegenOptions; - linterOptions; - minifierOptions; + runOptions: OxcRunOptions; + parserOptions: OxcParserOptions; + codegenOptions: OxcCodegenOptions; + linterOptions: OxcLinterOptions; + minifierOptions: OxcMinifierOptions; - editor; - viewer; - currentView = "ast"; // "ast" | "format" | "minify" | "ir" - languageConf; - urlParams; - viewerIsEditableConf; + editor: EditorView; + viewer: EditorView; + currentView: EditorViewKind = "ast"; // "ast" | "format" | "minify" | "ir" + languageConf: Compartment; + linterConf: Compartment; + urlParams: URLParams; + viewerIsEditableConf: Compartment; constructor() { this.languageConf = new Compartment(); @@ -133,7 +146,7 @@ class Playground { this.linterConf = new Compartment(); this.editor = this.initEditor(); this.viewer = this.initViewer(); - this.currentView = getStringFromStorage(ACTIVE_TAB_STORAGE_KEY_CODE) || "ast"; + this.currentView = (getStringFromStorage(ACTIVE_TAB_STORAGE_KEY_CODE) as EditorViewKind | null) || "ast"; } initOxc() { @@ -156,7 +169,7 @@ class Playground { return linter(() => this.updateDiagnostics(), { delay: 0 }) } - runOxc(text) { + runOxc(text: string | undefined) { const sourceText = text; this.urlParams.updateCode(sourceText); this.oxc.sourceText = sourceText; @@ -328,7 +341,7 @@ class Playground { this.minifierOptions, ); const elapsed = new Date() - start; - document.getElementById("duration").innerText = `${elapsed}ms`; + document.getElementById("duration")!.innerText = `${elapsed}ms`; } currentLanguage() { @@ -343,8 +356,12 @@ class Playground { } } - updatePanel(diagnostics) { + updatePanel(diagnostics: Diagnostic[]) { const panel = document.getElementById("panel"); + if (!panel) { + console.error(new Error('No #panel found')) + return + } panel.innerText = diagnostics .map((d) => { const emoji = { @@ -358,7 +375,7 @@ class Playground { panel.scrollTop = panel.scrollHeight; } - updateView(view) { + updateView(view?: EditorViewKind) { view = view || this.currentView; this.currentView = view; @@ -369,7 +386,7 @@ class Playground { this.runOptions.format = false; this.runOptions.minify = false; - let text; + let text: string; switch (this.currentView) { case "ast": this.run(); @@ -415,25 +432,28 @@ class Playground { this.run(); text = this.oxc.formattedText; break; + default: + console.warn('Unknown view', this.currentView) + return } this.updateEditorText(this.viewer, text); } - updateEditorText(instance, text) { + updateEditorText(instance: EditorView, text: string) { const transaction = instance.state.update({ changes: { from: 0, to: instance.state.doc.length, insert: text }, }); instance.dispatch(transaction); } - highlightEditorRange(view, range) { + highlightEditorRange(view: EditorView, range: Range | Range[]) { let ranges = Array.isArray(range) ? range : [range]; ranges = ranges.filter((range) => range.from !== 0 || range.to !== 0); if (ranges.length === 0) { return; } - const addHighlight = StateEffect.define({ + const addHighlight = StateEffect.define>({ map: ({ from, to }, change) => ({ from: change.mapPos(from), to: change.mapPos(to), @@ -465,7 +485,7 @@ class Playground { view.dispatch({ effects }); } - getTextFromView(view, from, to) { + getTextFromView(view: EditorView, from: number, to?: number) { return view.state.doc.sliceString(from, to); } @@ -475,7 +495,7 @@ class Playground { }); // Highlight the editor by searching for `start` and `end` values. - highlightEditorFromViewer(e, view) { + highlightEditorFromViewer(e: MouseEvent, view: EditorView) { if (this.currentView === 'symbol') { const pos = view.posAtCoords(e); const tree = syntaxTree(view.state); @@ -684,7 +704,7 @@ function addHorizontalResize() { } globalThis.document.body.style.cursor = 'col-resize'; - const moveHandler = event => { + const moveHandler = (event: MouseEvent) => { event.preventDefault(); const newPosition = ( event.pageX - offset) / size * 100; // Using 99% as the max value prevents the divider from disappearing diff --git a/website/playground/symbols.js b/website/playground/symbols.js deleted file mode 100644 index 64441cda1652..000000000000 --- a/website/playground/symbols.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @typedef {Object} SymbolTable - * @property {Array<{start: number, end: number}>} spans - The spans of the symbols. - * @property {string[]} names - The names of the symbols. - * @property {string[]} flags - The flags of the symbols. - * @property {number[]} scopeIds - The scope IDs of the symbols. - * @property {number[]} declarations - The declarations of the symbols. - * @property {Array} resolvedReferences - The resolved references of the symbols. - * @property {Array<{span: {start: number, end: number}, name: string, node_id: number, symbol_id: number|null, flag: string}>} references - The references of the symbols. - */ - -/** - * @type {Array} - */ -let cacheSymbols = null -/** - * - * @param {SymbolTable} symbols - * @returns - */ -export const renderSymbols = (symbols) => { - const target = [] - symbols.declarations.forEach((nodeId, index) => { - target.push({ - name: symbols.names[index], - flag: symbols.flags[index], - symbolId: index, - nodeId, - span: symbols.spans[index], - references: symbols.resolvedReferences[index].map((id) => ({ referenceId: id, ...symbols.references[id] })), - }) - }) - cacheSymbols = target - return JSON.stringify(target, null, 2) -} - -export const getSymbolAndReferencesSpan = (start, end) => { - if (!cacheSymbols) { - return [{ start, end }] - } - const symbol = cacheSymbols.find((symbol) => { - return symbol.span.start == start && symbol.span.end == end - }) - - if (!symbol) { - return [{ start, end }] - } - - return [symbol.span, ...symbol.references.map((reference) => reference.span)] -} \ No newline at end of file diff --git a/website/playground/symbols.ts b/website/playground/symbols.ts new file mode 100644 index 000000000000..d65f7931fb77 --- /dev/null +++ b/website/playground/symbols.ts @@ -0,0 +1,64 @@ +import type { SymbolTable } from "@oxc/oxc_wasm"; + +type Span = { start: number; end: number } + +type RenderedSymbol = { + name: string + flag: string + symbolId: number + nodeId: number + span: Span + references: Array<{ + referenceId: number + span: Span + name: string + nodeId: number + symbolId: number | null + flag: string + }> + +} + +let cacheSymbols: RenderedSymbol[] | null = null + +/** + * + * @param {SymbolTable} symbols + * @returns + */ +export const renderSymbols = (symbols: SymbolTable): string => { + const target = symbols.declarations.reduce( + (acc, nodeId, index) => { + acc.push({ + name: symbols.names[index], + flag: symbols.flags[index], + symbolId: index, + nodeId, + span: symbols.spans[index], + references: symbols.resolvedReferences[index].map(id => ({ + referenceId: id, + ...symbols.references[id], + })), + }) + return acc + }, + [] as RenderedSymbol[] + ) + cacheSymbols = target + return JSON.stringify(target, null, 2) +} + +export const getSymbolAndReferencesSpan = (start: number, end: number): Span[] => { + if (!cacheSymbols) { + return [{ start, end }] + } + const symbol = cacheSymbols.find(symbol => { + return symbol.span.start == start && symbol.span.end == end + }) + + if (!symbol) { + return [{ start, end }] + } + + return [symbol.span, ...symbol.references.map(reference => reference.span)] +} diff --git a/website/playground/traverseJson.js b/website/playground/traverseJson.ts similarity index 62% rename from website/playground/traverseJson.js rename to website/playground/traverseJson.ts index b5f16da15d3b..fa0158f4ec07 100644 --- a/website/playground/traverseJson.js +++ b/website/playground/traverseJson.ts @@ -1,15 +1,16 @@ +import { SyntaxNode } from "@lezer/common"; -let typeFilter = ["JsonText", "Object", "Property", "Array"]; -/** - * @param {import('@lezer/common').SyntaxNode} node - * @returns {import('@lezer/common').SyntaxNode} - * - * */ -export function findMostInnerNodeForPosition(node, offset, source) { +const typeFilter = ["JsonText", "Object", "Property", "Array"]; + +export function findMostInnerNodeForPosition( + node: SyntaxNode, + offset: number, + source: string +): SyntaxNode | undefined { if (!typeFilter.includes(node.name)) { return; } - let targetNode; + let targetNode: SyntaxNode | undefined; if (node.name === "Object") { let span = getSpanOfNode(node, source); if (Object.keys(span).length > 0) { @@ -36,11 +37,11 @@ export function findMostInnerNodeForPosition(node, offset, source) { * @param {import('@lezer/common').SyntaxNode} node * @param {string} source * */ -function getSpanOfNode(node, source) { - let span = {}; - let child = node.firstChild; - while (child) { - if (child.name === "Property" && child.firstChild.name === "PropertyName") { +function getSpanOfNode(node: SyntaxNode, source: string) { + let span = {} as Record; + + for (let child = node.firstChild; !!child; child = child.nextSibling) { + if (child.name === "Property" && child.firstChild?.name === "PropertyName") { let { from, to } = child.firstChild; let name = source.slice(from + 1, to - 1); if (["start", "end"].includes(name)) { @@ -50,7 +51,7 @@ function getSpanOfNode(node, source) { } } } - child = child.nextSibling; } + return span; } diff --git a/website/pnpm-lock.yaml b/website/pnpm-lock.yaml index b095cf0aef36..0d6a52826e8d 100644 --- a/website/pnpm-lock.yaml +++ b/website/pnpm-lock.yaml @@ -60,12 +60,21 @@ importers: '@oxc/oxc_wasm': specifier: link:../npm/oxc-wasm version: link:../npm/oxc-wasm + '@types/lodash.throttle': + specifier: ^4.1.9 + version: 4.1.9 + '@types/lzma': + specifier: ^2.3.0 + version: 2.3.0 concurrently: specifier: ^8.2.2 version: 8.2.2 + oxlint: + specifier: link:../apps/oxlint + version: link:../apps/oxlint vite: specifier: ^5.2.13 - version: 5.2.13 + version: 5.2.13(@types/node@20.14.2) wasm-pack: specifier: ^0.12.1 version: 0.12.1 @@ -364,6 +373,18 @@ packages: '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + '@types/lodash.throttle@4.1.9': + resolution: {integrity: sha512-PCPVfpfueguWZQB7pJQK890F2scYKoDUL3iM522AptHWn7d5NQmeS/LTEHIcLr5PaTzl3dK2Z0xSUHHTHwaL5g==} + + '@types/lodash@4.17.5': + resolution: {integrity: sha512-MBIOHVZqVqgfro1euRDWX7OO0fBVUUMrN6Pwm8LQsz8cWhEpihlvR70ENj3f40j58TNxZaWv2ndSkInykNBBJw==} + + '@types/lzma@2.3.0': + resolution: {integrity: sha512-z7TknP6ts5GPnN7P2bkZC1B/tMpoMJXG3UnPY1XDrwBq1OJQ2EpHtEypFwq5Q0A5iS37+oc+MT/o/B7x5lgl8Q==} + + '@types/node@20.14.2': + resolution: {integrity: sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -587,6 +608,9 @@ packages: tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + vite@5.2.13: resolution: {integrity: sha512-SSq1noJfY9pR3I1TUENL3rQYDQCFqgD+lM6fTRAM8Nv6Lsg5hDLaXkjETVeBt+7vZBCMoibD+6IWnT2mJ+Zb/A==} engines: {node: ^18.0.0 || >=20.0.0} @@ -877,6 +901,20 @@ snapshots: '@types/estree@1.0.5': {} + '@types/lodash.throttle@4.1.9': + dependencies: + '@types/lodash': 4.17.5 + + '@types/lodash@4.17.5': {} + + '@types/lzma@2.3.0': + dependencies: + '@types/node': 20.14.2 + + '@types/node@20.14.2': + dependencies: + undici-types: 5.26.5 + ansi-regex@5.0.1: {} ansi-styles@4.3.0: @@ -1130,12 +1168,15 @@ snapshots: tslib@2.6.2: {} - vite@5.2.13: + undici-types@5.26.5: {} + + vite@5.2.13(@types/node@20.14.2): dependencies: esbuild: 0.20.2 postcss: 8.4.38 rollup: 4.18.0 optionalDependencies: + '@types/node': 20.14.2 fsevents: 2.3.3 w3c-keyname@2.2.8: {} diff --git a/website/tsconfig.json b/website/tsconfig.json new file mode 100644 index 000000000000..f98154c98e36 --- /dev/null +++ b/website/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + } +}