From e8fbf906760699617649dff8219797db30875653 Mon Sep 17 00:00:00 2001 From: Tom Selfin Date: Sat, 1 Nov 2025 15:40:22 +0200 Subject: [PATCH 1/4] use import instead of require in Installer --- src/FileTree.ts | 3 +-- src/Installer.ts | 28 ++++++++++++++-------------- src/test/suite/Installer.test.ts | 8 +++++--- tsconfig.json | 5 +++-- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/FileTree.ts b/src/FileTree.ts index d3db0e77..648bbb6c 100644 --- a/src/FileTree.ts +++ b/src/FileTree.ts @@ -1,10 +1,9 @@ import * as vscode from "vscode"; import { Block, getQueryBlocks } from "./BlockTree"; -import Parser, { Query, SyntaxNode, Tree } from "tree-sitter"; +import Parser, { type Language, Query, SyntaxNode, Tree } from "tree-sitter"; import { Result, err, ok } from "./result"; -import { Language } from "./Installer"; import { Selection } from "./Selection"; import { getLanguageConfig } from "./configuration"; import { getLogger } from "./outputChannel"; diff --git a/src/Installer.ts b/src/Installer.ts index f25e92e6..e855e2d3 100644 --- a/src/Installer.ts +++ b/src/Installer.ts @@ -5,15 +5,13 @@ import * as vscode from "vscode"; import { ExecException, ExecOptions, exec } from "child_process"; import { Result, err, ok } from "./result"; import { existsSync, rmSync } from "fs"; -import Parser from "tree-sitter"; +import type { Language } from "tree-sitter"; import { getLogger } from "./outputChannel"; import { mkdir } from "fs/promises"; import which from "which"; const NPM_INSTALL_URL = "https://nodejs.org/en/download"; -export type Language = Parser.Language; - export function getAbsoluteParserDir(parsersDir: string, parserName: string): string { return path.resolve(path.join(parsersDir, parserName)); } @@ -22,11 +20,11 @@ export function getAbsoluteBindingsDir(parsersDir: string, parserName: string): return path.resolve(path.join(parsersDir, parserName, "bindings", "node", "index.js")); } -export function loadParser( +export async function loadParser( parsersDir: string, parserName: string, subdirectory?: string -): Result { +): Promise> { const logger = getLogger(); const bindingsDir = getAbsoluteBindingsDir(parsersDir, parserName); @@ -39,12 +37,14 @@ export function loadParser( try { logger.log(`Loading parser from ${bindingsDir}`); - // using dynamic import causes issues on windows - // make sure to test well on windows before changing this - // TODO(02/11/24): change to dynamic import - // let { default: language } = (await import(bindingsDir)) as { default: Language }; - // eslint-disable-next-line @typescript-eslint/no-require-imports - let language = require(bindingsDir) as Language; + let language: Language; + try { + language = ((await import(bindingsDir)) as { default: Language }).default; + } catch (error) { + // TODO(1/11/25): Always use import and remove this backup + // eslint-disable-next-line @typescript-eslint/no-var-requires + language = require(bindingsDir) as Language; + } logger.log(`Got language: ${JSON.stringify(Object.keys(language))}`); @@ -117,7 +117,7 @@ export async function downloadAndBuildParser( } // try to load parser optimistically - const loadResult = loadParser(parsersDir, parserName); + const loadResult = await loadParser(parsersDir, parserName); if (loadResult.status === "ok") { return ok(undefined); } @@ -136,7 +136,7 @@ export async function downloadAndBuildParser( } // if it fails, try to build it - const buildResult = await runCmd(`${treeSitterCli} generate`, { cwd: parserDir }, (d) => + const buildResult = await runCmd(`${treeSitterCli} build`, { cwd: parserDir }, (d) => onData?.(d.toString()) ); if (buildResult.status === "err") { @@ -272,7 +272,7 @@ export async function getLanguage( } } - const loadResult = loadParser(parsersDir, parserName, subdirectory); + const loadResult = await loadParser(parsersDir, parserName, subdirectory); if (loadResult.status === "err") { const msg = `Failed to load parser for language ${languageId} > ${loadResult.result}`; diff --git a/src/test/suite/Installer.test.ts b/src/test/suite/Installer.test.ts index 0b688c22..ea0e4bfe 100644 --- a/src/test/suite/Installer.test.ts +++ b/src/test/suite/Installer.test.ts @@ -4,10 +4,12 @@ import * as vscode from "vscode"; import Parser from "tree-sitter"; import { TreeViewer } from "../../TreeViewer"; import { openDocument } from "./testUtils"; +import path from "path"; export async function testParser(language: string, content?: string): Promise { // fail the test if the parser could not be installed - const result = await Installer.getLanguage("test-parsers", language, true); + const testParsersDir = path.resolve(__dirname, "..", "..", "..", "test-parsers"); + const result = await Installer.getLanguage(testParsersDir, language, true); if (result.status === "err") { throw new Error(`Failed to install language: ${JSON.stringify(result.result)}`); } @@ -49,7 +51,7 @@ suite("Installer integration tests", function () { ["Ruby", "ruby", "def foo\nend\ndef bar\nend"], // ["SQL", "sql"], ["HTML", "html", ""], - // ["CSS", "css", "body { color: red; }"], + ["CSS", "css", "body { color: red; }"], ["YAML", "yaml", "key: value"], ["JSON", "json", '{ "key": "value" }'], ["XML", "xml"], @@ -57,7 +59,7 @@ suite("Installer integration tests", function () { // ["LaTeX", "latex"], ["Bash", "shellscript", "echo 'Hello, World!'"], ["TOML", "toml"], - // ["Swift", "swift"], + ["Swift", "swift"], ["Kotlin", "kotlin", "fun main() { }"], ["Zig", "zig", 'const std = @import("std");\n\npub fn main() void { }'], ]; diff --git a/tsconfig.json b/tsconfig.json index 8867d3a9..e189d3e6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,8 @@ { "compilerOptions": { - "module": "commonjs", - "target": "ES2022", + "module": "nodenext", + "moduleResolution": "nodenext", + "target": "ES2020", "outDir": "out", "lib": [ "ES2022", From 0733380652523c78d9f05516b8919ba2144a0ad3 Mon Sep 17 00:00:00 2001 From: Tom Selfin Date: Sat, 1 Nov 2025 19:59:57 +0200 Subject: [PATCH 2/4] update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2862e654..36e230fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ - `toml` and `xml` support, thanks [@sanarise](https://github.com/selfint/code-blocks/pull/186)! +- `css` support ([#191](https://github.com/selfint/code-blocks/issues/191)) by switching to more robust Parser loading method. + ## Fixed - Add 'Remove' option to notification if parser fails to load ([#180](https://github.com/selfint/code-blocks/issues/180)). From b9d2d6fb52759caae6d0178fac15616e1cc70241 Mon Sep 17 00:00:00 2001 From: Tom Selfin Date: Sat, 1 Nov 2025 20:00:01 +0200 Subject: [PATCH 3/4] update readme --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 49ed4dd2..0549136b 100644 --- a/README.md +++ b/README.md @@ -45,10 +45,10 @@ View your code's syntax tree directly - `node` / `npm`: Used to download tree-sitter language parsers. Can be installed from [here](https://nodejs.org/en/download). -- **OPTIONAL:** `tree-sitter` / `node-gyp-build`: Used for tree-sitter language parsers that need to be locally built. +- **OPTIONAL:** `tree-sitter`: Used for tree-sitter language parsers that need to be locally built. After installing `npm`, can be installed by running: - `npm i -g tree-sitter-cli node-gyp-build`. + `npm i -g tree-sitter-cli`. If you don't want to install these tools, there's a good chance you don't need them. Try the extension without it, it will notify you if they are required. @@ -117,8 +117,7 @@ Or [create a pull request](https://github.com/selfint/code-blocks/pulls) with yo - `codeBlocks.npmPackageName`: [NPM](https://www.npmjs.com/) package name of the `tree-sitter` parser to use for the language. Defaults to `tree-sitter-`, change if the package name doesn't match the languageId. -- `codeBlocks.parserName`: Filename of the WASM parser built by the `tree-sitter build --wasm` command, without the - `.wasm` extension. Defaults to `tree-sitter-`, change if the parser filename doesn't match the languageId. +- `codeBlocks.parserName`: Name to save parser as (defaults to `tree-sitter-`), change if the package name doesn't match the languageId (e.g., `tree-sitter-typescript` for `[typescriptreact]` languageId). - `codeBlocks.subdirectory`: Directory inside the NPM package containing the `tree-sitter` grammar. Defaults to the root directory of the package, change if the grammar isn't there. From eb47d02617f85b1501635b1c4a06ee618724db8f Mon Sep 17 00:00:00 2001 From: Tom Selfin Date: Sat, 1 Nov 2025 20:07:52 +0200 Subject: [PATCH 4/4] cleanup code --- eslint.config.mjs | 2 ++ src/Installer.ts | 4 ++-- src/examples/suite/index.ts | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 80474f07..1c147d90 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -44,6 +44,8 @@ export default defineConfig([ "error", { argsIgnorePattern: "^_.*", + varsIgnorePattern: "^_", + caughtErrorsIgnorePattern: "^_", }, ], diff --git a/src/Installer.ts b/src/Installer.ts index e855e2d3..e9dc6dad 100644 --- a/src/Installer.ts +++ b/src/Installer.ts @@ -40,9 +40,9 @@ export async function loadParser( let language: Language; try { language = ((await import(bindingsDir)) as { default: Language }).default; - } catch (error) { + } catch (_) { // TODO(1/11/25): Always use import and remove this backup - // eslint-disable-next-line @typescript-eslint/no-var-requires + // eslint-disable-next-line @typescript-eslint/no-require-imports language = require(bindingsDir) as Language; } diff --git a/src/examples/suite/index.ts b/src/examples/suite/index.ts index 5c5059e1..fcabcb41 100644 --- a/src/examples/suite/index.ts +++ b/src/examples/suite/index.ts @@ -1,6 +1,6 @@ import * as path from "path"; import Mocha from "mocha"; -import { glob } from "glob"; // ✅ named import for v10+ +import { glob } from "glob"; export async function run(): Promise { let example = process.env.EXAMPLE;