Skip to content

Commit ce0f766

Browse files
authored
feat: Add editor SDK support for oxc (oxfmt & oxlint) (#7078)
## What's the problem this PR addresses? Add editor SDK support for oxc. This should enable optional editor integration with both oxfmt and oxlint. ## How did you fix it? Added oxlint, oxfmt and oxlint-tsgolint as supported integration and documented it. Oxlint and oxlint-tsgolint addition is a bit hacky and I don't know whether this is the right direction or not. Rationale can be read from the comment there. Loader option added to sdk setupEnv to prevent oxfmt tinypool from being broken due to failing to resolve tinypool/dist/process.js. I haven't tried this with vim yet since I don't use it so if anyone would kindly help test whether this will work on vim or not, thanks! ### oxlint type-aware feature (tsgolint) The biggest challenge here is the type-aware feature (tsgolint) support. The rust side of oxlint spawn tsgolint a bit different from how the oxlint itself is spawned: https://github.com/oxc-project/oxc/blob/d3dcf5bc9718ebb4839be27062b5d82da2118e2e/crates/oxc_linter/src/tsgolint.rs#L265-L270 Instead of using js entrypoint, it directly spawn executable so the wrapper approach is not directly applicable here. Luckily, there are some strategies that the oxc_linter uses to resolve tsgolint: - via PATH - via node_modules search - direct path via OXLINT_TSGOLINT_PATH (which is what oxc.path.tsgolint does basically) The only realistic strategy is to use PATH. For the sdks, it will generate a windows executable shim (cmd) that will basically just spawn tsgolint.js, else should fallback to tsgolint. Be noted however that this will not fix fundamental issue with typescript-go module resolution, which requires this to be merged first: - microsoft/typescript-go#1966 This PR just enable the usage of tsgo regardless of whether it can resolve with pnp or not in the type-checking. ### Reference Coc config: https://github.com/oxc-project/coc-oxc/blob/7a7f0d8f503d5cdb65da37232da6b865f159b42a/src/common.ts#L27-L36 Vscode oxc binary resolution reference: https://github.com/oxc-project/oxc-vscode/blob/main/client/findBinary.ts Oxlint-tsgolint executable resolution: https://github.com/oxc-project/oxc/blob/d3dcf5bc9718ebb4839be27062b5d82da2118e2e/crates/oxc_linter/src/tsgolint.rs#L1164-L1225 ## Checklist - [x] I have read the [Contributing Guide](https://yarnpkg.com/advanced/contributing). - [x] I have set the packages that need to be released for my changes to be effective. - [x] I will check that all automated PR checks pass before the PR gets reviewed.
1 parent 02fe7a3 commit ce0f766

6 files changed

Lines changed: 150 additions & 1 deletion

File tree

.yarn/versions/92c64300.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
releases:
2+
"@yarnpkg/sdks": minor

packages/docusaurus/docs/getting-started/extra/editor-sdks.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ The `yarn dlx @yarnpkg/sdks` command will look at the content of your *root* `pa
5050
| [vscode-eslint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) | [eslint](https://yarnpkg.com/package/eslint) |
5151
| [prettier-vscode](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) | [prettier](https://yarnpkg.com/package/prettier) |
5252
| [relay](https://marketplace.visualstudio.com/items?itemName=meta.relay) | [relay](https://relay.dev/)
53+
| [oxc](https://marketplace.visualstudio.com/items?itemName=oxc.oxc-vscode) | [oxlint](https://yarnpkg.com/package/oxlint)
54+
| [oxc](https://marketplace.visualstudio.com/items?itemName=oxc.oxc-vscode) | [oxfmt](https://yarnpkg.com/package/oxfmt)
5355

5456
If you'd like to contribute more, [take a look here!](https://github.com/yarnpkg/berry/blob/master/packages/yarnpkg-sdks/sources/generateSdk.ts)
5557

packages/yarnpkg-sdks/sources/generateSdk.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@ const TEMPLATE = (relPnpApiPath: PortablePath, module: string, {setupEnv = false
145145
`\n`,
146146
` process.env.NODE_OPTIONS = process.env.NODE_OPTIONS || \`\`;\n`,
147147
` process.env.NODE_OPTIONS += \` -r \${absPnpApiPath}\`;\n`,
148+
` if (isPnpLoaderEnabled && register) {\n`,
149+
` process.env.NODE_OPTIONS += \` --experimental-loader \${pathToFileURL(absPnpLoaderPath)}\`;\n`,
150+
` }\n`,
148151
` }\n`,
149152
] : []),
150153
...(usePnpify ? [
@@ -201,7 +204,10 @@ export type SupportedSdk =
201204
| `typescript-language-server`
202205
| `typescript`
203206
| `svelte-language-server`
204-
| `flow-bin`;
207+
| `flow-bin`
208+
| `oxfmt`
209+
| `oxlint`
210+
| `oxlint-tsgolint`;
205211

206212
export type BaseSdks = Array<[
207213
SupportedSdk,
@@ -343,6 +349,21 @@ export class Wrapper {
343349
return absWrapperPath;
344350
}
345351

352+
async writeRaw(relPackagePath: PortablePath, content: string, options: {mode?: number} = {}) {
353+
const topLevelInformation = this.pnpApi.getPackageInformation(this.pnpApi.topLevel)!;
354+
const projectRoot = npath.toPortablePath(topLevelInformation.packageLocation);
355+
356+
const absPath = ppath.join(this.target, this.name, relPackagePath);
357+
const relProjectPath = ppath.relative(projectRoot, absPath);
358+
359+
await xfs.mkdirPromise(ppath.dirname(absPath), {recursive: true});
360+
await xfs.writeFilePromise(absPath, content, {mode: options.mode});
361+
362+
this.paths.set(relPackagePath, relProjectPath);
363+
364+
return absPath;
365+
}
366+
346367
getProjectPathTo(relPackagePath: PortablePath) {
347368
const relProjectPath = this.paths.get(relPackagePath);
348369

packages/yarnpkg-sdks/sources/sdks/base.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,67 @@ export const generateFlowBinBaseWrapper: GenerateBaseWrapper = async (pnpApi: Pn
285285
return wrapper;
286286
};
287287

288+
export const generateOxfmtBaseWrapper: GenerateBaseWrapper = async (pnpApi: PnpApi, target: PortablePath) => {
289+
const wrapper = new Wrapper(`oxfmt` as PortablePath, {pnpApi, target});
290+
291+
const oxfmtMonkeyPatch = `
292+
module => module;
293+
294+
const binPath = resolve(absRequire.resolve(\`oxfmt/package.json\`), \`..\`, \`${wrapper.manifest.bin.oxfmt}\`);
295+
absRequire(binPath);
296+
`;
297+
298+
await wrapper.writeDefaults();
299+
await wrapper.writeBinary(`bin/oxfmt` as PortablePath, {
300+
setupEnv: true,
301+
requirePath: `` as PortablePath,
302+
wrapModule: oxfmtMonkeyPatch,
303+
});
304+
305+
return wrapper;
306+
};
307+
308+
export const generateOxlintBaseWrapper: GenerateBaseWrapper = async (pnpApi: PnpApi, target: PortablePath) => {
309+
const wrapper = new Wrapper(`oxlint` as PortablePath, {pnpApi, target});
310+
311+
// There are two workarounds here:
312+
// 1. Injecting into PATH to enable tsgolint's PATH resolution strategy in the following tsgolint wrapper.
313+
// 2. Direct file import to work around the top-level await and exports restrictions in oxlint.
314+
const oxlintMonkeyPatch = `
315+
module => module;
316+
317+
process.env.PATH += \`;\${resolve(__dirname, \`../../oxlint-tsgolint/bin\`)}\`;
318+
319+
const binPath = resolve(absRequire.resolve(\`oxlint/package.json\`), \`..\`, \`${wrapper.manifest.bin.oxlint}\`);
320+
import(pathToFileURL(binPath));
321+
`;
322+
323+
await wrapper.writeDefaults();
324+
// This intentionally produces a dummy export; the main logic is in the import above.
325+
// Originally, the binary does not export anything.
326+
await wrapper.writeBinary(`bin/oxlint` as PortablePath, {requirePath: `` as PortablePath, wrapModule: oxlintMonkeyPatch});
327+
328+
return wrapper;
329+
};
330+
331+
export const generateOxlintTsgolintBaseWrapper: GenerateBaseWrapper = async (pnpApi: PnpApi, target: PortablePath) => {
332+
const wrapper = new Wrapper(`oxlint-tsgolint` as PortablePath, {pnpApi, target});
333+
334+
// We are using the oxc_linter tsgolint resolution mechanism via the PATH environment variable
335+
// since it's the only realistic approach to correctly resolve the tsgolint binary when using Yarn PnP.
336+
// Ref: https://github.com/oxc-project/oxc/blob/d3dcf5bc9718ebb4839be27062b5d82da2118e2e/crates/oxc_linter/src/tsgolint.rs#L1164-L1225
337+
// With this approach, we need to manually create both Unix and Windows executable shim for tsgolint.js.
338+
const tsgolintCmd = `
339+
@goto #_undefined_# 2>NUL || @title %COMSPEC% & @setlocal & @"node" "%~dp0tsgolint.js" %*
340+
`.trim().replace(/^ {4}/gm, ``);
341+
342+
await wrapper.writeDefaults();
343+
await wrapper.writeBinary(`bin/tsgolint` as PortablePath);
344+
await wrapper.writeRaw(`bin/tsgolint.cmd` as PortablePath, tsgolintCmd, {mode: 0o755});
345+
346+
return wrapper;
347+
};
348+
288349
export const BASE_SDKS: BaseSdks = [
289350
[`@astrojs/language-server`, generateAstroLanguageServerBaseWrapper],
290351
[`eslint`, generateEslintBaseWrapper],
@@ -294,4 +355,7 @@ export const BASE_SDKS: BaseSdks = [
294355
[`typescript`, generateTypescriptBaseWrapper],
295356
[`svelte-language-server`, generateSvelteLanguageServerBaseWrapper],
296357
[`flow-bin`, generateFlowBinBaseWrapper],
358+
[`oxfmt`, generateOxfmtBaseWrapper],
359+
[`oxlint`, generateOxlintBaseWrapper],
360+
[`oxlint-tsgolint`, generateOxlintTsgolintBaseWrapper],
297361
];

packages/yarnpkg-sdks/sources/sdks/cocvim.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,29 @@ export const generateTypescriptWrapper: GenerateIntegrationWrapper = async (pnpA
3737
});
3838
};
3939

40+
export const generateOxfmtWrapper: GenerateIntegrationWrapper = async (pnpApi: PnpApi, target: PortablePath, wrapper: Wrapper) => {
41+
await addCocVimWorkspaceConfiguration(pnpApi, CocVimConfiguration.settings, {
42+
[`oxc.oxfmt.binPath`]: npath.fromPortablePath(
43+
wrapper.getProjectPathTo(
44+
ppath.normalize(wrapper.manifest.bin.oxfmt),
45+
),
46+
),
47+
});
48+
};
49+
50+
export const generateOxlintWrapper: GenerateIntegrationWrapper = async (pnpApi: PnpApi, target: PortablePath, wrapper: Wrapper) => {
51+
await addCocVimWorkspaceConfiguration(pnpApi, CocVimConfiguration.settings, {
52+
[`oxc.oxlint.binPath`]: npath.fromPortablePath(
53+
wrapper.getProjectPathTo(
54+
ppath.normalize(wrapper.manifest.bin.oxlint),
55+
),
56+
),
57+
});
58+
};
59+
4060
export const COC_VIM_SDKS: IntegrationSdks = [
4161
[`eslint`, generateEslintWrapper],
4262
[`typescript`, generateTypescriptWrapper],
63+
[`oxfmt`, generateOxfmtWrapper],
64+
[`oxlint`, generateOxlintWrapper],
4365
];

packages/yarnpkg-sdks/sources/sdks/vscode.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,41 @@ export const generateFlowBinWrapper: GenerateIntegrationWrapper = async (pnpApi:
139139
});
140140
};
141141

142+
export const generateOxfmtWrapper: GenerateIntegrationWrapper = async (pnpApi: PnpApi, target: PortablePath, wrapper: Wrapper) => {
143+
await addVSCodeWorkspaceConfiguration(pnpApi, VSCodeConfiguration.settings, {
144+
[`oxc.path.oxfmt`]: npath.fromPortablePath(
145+
wrapper.getProjectPathTo(
146+
ppath.normalize(wrapper.manifest.bin.oxfmt),
147+
),
148+
),
149+
});
150+
151+
await addVSCodeWorkspaceConfiguration(pnpApi, VSCodeConfiguration.extensions, {
152+
[`recommendations`]: [
153+
`oxc.oxc-vscode`,
154+
],
155+
});
156+
};
157+
158+
export const generateOxlintWrapper: GenerateIntegrationWrapper = async (pnpApi: PnpApi, target: PortablePath, wrapper: Wrapper) => {
159+
await addVSCodeWorkspaceConfiguration(pnpApi, VSCodeConfiguration.settings, {
160+
[`oxc.path.oxlint`]: npath.fromPortablePath(
161+
wrapper.getProjectPathTo(
162+
ppath.normalize(wrapper.manifest.bin.oxlint),
163+
),
164+
),
165+
});
166+
167+
await addVSCodeWorkspaceConfiguration(pnpApi, VSCodeConfiguration.extensions, {
168+
[`recommendations`]: [
169+
`oxc.oxc-vscode`,
170+
],
171+
});
172+
};
173+
174+
export const generateOxlintTsgolintWrapper: GenerateIntegrationWrapper = async (pnpApi: PnpApi, target: PortablePath, wrapper: Wrapper) => {
175+
};
176+
142177
export const VSCODE_SDKS: IntegrationSdks = [
143178
[null, generateDefaultWrapper],
144179
[`@astrojs/language-server`, generateAstroLanguageServerWrapper],
@@ -149,4 +184,7 @@ export const VSCODE_SDKS: IntegrationSdks = [
149184
[`typescript`, generateTypescriptWrapper],
150185
[`svelte-language-server`, generateSvelteLanguageServerWrapper],
151186
[`flow-bin`, generateFlowBinWrapper],
187+
[`oxfmt`, generateOxfmtWrapper],
188+
[`oxlint`, generateOxlintWrapper],
189+
[`oxlint-tsgolint`, generateOxlintTsgolintWrapper],
152190
];

0 commit comments

Comments
 (0)