From c120969d9163fc522ff3f6b613e4dc4e8f418559 Mon Sep 17 00:00:00 2001 From: Vijaya Krishna <8697234+Viijay-Kr@users.noreply.github.com> Date: Sat, 8 Jun 2024 13:23:34 +0200 Subject: [PATCH] feat(tsconfig): json5 to parse tsconfig (#157) --- .changeset/little-chairs-provide.md | 5 +++ .../test/aliased-modules/AliasedModules.tsx | 10 +++++ examples/react-app/tsconfig.json | 18 ++++----- package-lock.json | 39 ++++++++++++++----- package.json | 17 ++++---- src/extension.ts | 16 +++++--- src/parser/v2/css.ts | 5 ++- src/providers/css/CSSProvider.ts | 11 +++++- src/providers/css/codelens.ts | 5 ++- src/providers/css/rename-selector.ts | 8 +++- src/providers/ts/code-actions.ts | 5 ++- src/providers/ts/completion.ts | 13 +++++-- src/providers/ts/definitions.ts | 8 +++- src/providers/ts/hover.ts | 7 +++- src/store/Store.ts | 24 +++++++++--- src/test/suite/extension.test.ts | 38 ++++++++++++++++++ 16 files changed, 176 insertions(+), 53 deletions(-) create mode 100644 .changeset/little-chairs-provide.md create mode 100644 examples/react-app/src/test/aliased-modules/AliasedModules.tsx diff --git a/.changeset/little-chairs-provide.md b/.changeset/little-chairs-provide.md new file mode 100644 index 0000000..a4ea0bf --- /dev/null +++ b/.changeset/little-chairs-provide.md @@ -0,0 +1,5 @@ +--- +"react-ts-css": minor +--- + +feat(tsconfig): json5 to parse tsconfig diff --git a/examples/react-app/src/test/aliased-modules/AliasedModules.tsx b/examples/react-app/src/test/aliased-modules/AliasedModules.tsx new file mode 100644 index 0000000..48c2e45 --- /dev/null +++ b/examples/react-app/src/test/aliased-modules/AliasedModules.tsx @@ -0,0 +1,10 @@ +import styles from "@d/DuplicateSelectors.module.scss"; +import buttonCss from "@s/button.module.scss"; + +export default function () { + return ( +
+

+
+ ); +} diff --git a/examples/react-app/tsconfig.json b/examples/react-app/tsconfig.json index 2a75e02..0cf445e 100644 --- a/examples/react-app/tsconfig.json +++ b/examples/react-app/tsconfig.json @@ -2,11 +2,7 @@ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], + "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, "esModuleInterop": false, @@ -19,14 +15,16 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "baseUrl": "src" + "baseUrl": "src", + "paths": { + "@d": ["./test/DuplicateSelectors/*"], + "@s": ["./styles/*"] + } }, - "include": [ - "src" - ], + "include": ["src"], "references": [ { "path": "./tsconfig.node.json" } ] -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index e102204..47fae4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "react-ts-css", - "version": "2.6.0", + "version": "3.1.6", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "react-ts-css", - "version": "2.6.0", + "version": "3.1.6", "license": "MIT", "dependencies": { "typescript-cleanup-definitions": "^1.1.0" @@ -21,7 +21,7 @@ "@types/mocha": "^9.1.1", "@types/node": "^16.18.11", "@types/sinon": "^10.0.13", - "@types/vscode": "^1.72.0", + "@types/vscode": "^1.90.0", "@typescript-eslint/eslint-plugin": "^5.31.0", "@typescript-eslint/parser": "^5.31.0", "@vscode/test-electron": "^2.3.8", @@ -30,6 +30,7 @@ "eslint": "^8.20.0", "fast-glob": "^3.2.12", "glob": "^8.0.3", + "json5": "^2.2.3", "mocha": "^10.0.0", "ovsx": "^0.8.3", "sinon": "^14.0.1", @@ -41,7 +42,7 @@ "webpack-cli": "^4.10.0" }, "engines": { - "vscode": "^1.72.0" + "vscode": "^1.90.0" } }, "node_modules/@babel/code-frame": { @@ -1314,9 +1315,9 @@ "dev": true }, "node_modules/@types/vscode": { - "version": "1.72.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.72.0.tgz", - "integrity": "sha512-WvHluhUo+lQvE3I4wUagRpnkHuysB4qSyOQUyIAS9n9PYMJjepzTUD8Jyks0YeXoPD0UGctjqp2u84/b3v6Ydw==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.90.0.tgz", + "integrity": "sha512-oT+ZJL7qHS9Z8bs0+WKf/kQ27qWYR3trsXpq46YDjFqBsMLG4ygGGjPaJ2tyrH0wJzjOEmDyg9PDJBBhWg9pkQ==", "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { @@ -4504,6 +4505,18 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jsonc-parser": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", @@ -9002,9 +9015,9 @@ "dev": true }, "@types/vscode": { - "version": "1.72.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.72.0.tgz", - "integrity": "sha512-WvHluhUo+lQvE3I4wUagRpnkHuysB4qSyOQUyIAS9n9PYMJjepzTUD8Jyks0YeXoPD0UGctjqp2u84/b3v6Ydw==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.90.0.tgz", + "integrity": "sha512-oT+ZJL7qHS9Z8bs0+WKf/kQ27qWYR3trsXpq46YDjFqBsMLG4ygGGjPaJ2tyrH0wJzjOEmDyg9PDJBBhWg9pkQ==", "dev": true }, "@typescript-eslint/eslint-plugin": { @@ -11337,6 +11350,12 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, "jsonc-parser": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", diff --git a/package.json b/package.json index 55c3d03..95c7e30 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "theme": "dark" }, "engines": { - "vscode": "^1.72.0" + "vscode": "^1.90.0" }, "categories": [ "Programming Languages" @@ -180,6 +180,8 @@ "yarn": false }, "devDependencies": { + "@babel/parser": "^7.19.3", + "@babel/traverse": "^7.19.3", "@babel/types": "^7.19.3", "@changesets/cli": "^2.27.1", "@types/babel__traverse": "^7.18.2", @@ -187,26 +189,25 @@ "@types/mocha": "^9.1.1", "@types/node": "^16.18.11", "@types/sinon": "^10.0.13", - "@types/vscode": "^1.72.0", + "@types/vscode": "^1.90.0", "@typescript-eslint/eslint-plugin": "^5.31.0", "@typescript-eslint/parser": "^5.31.0", "@vscode/test-electron": "^2.3.8", "@vscode/vsce": "^2.16.0", + "closest-match": "^1.3.3", "eslint": "^8.20.0", + "fast-glob": "^3.2.12", "glob": "^8.0.3", + "json5": "^2.2.3", "mocha": "^10.0.0", "ovsx": "^0.8.3", "sinon": "^14.0.1", "ts-loader": "^9.3.1", "typescript": "^4.9.4", "vsce": "^2.11.0", - "webpack": "^5.74.0", - "webpack-cli": "^4.10.0", "vscode-css-languageservice": "^6.2.12", - "@babel/parser": "^7.19.3", - "@babel/traverse": "^7.19.3", - "closest-match": "^1.3.3", - "fast-glob": "^3.2.12" + "webpack": "^5.74.0", + "webpack-cli": "^4.10.0" }, "dependencies": { "typescript-cleanup-definitions": "^1.1.0" diff --git a/src/extension.ts b/src/extension.ts index 75e1f14..0201fef 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -59,10 +59,6 @@ workspace.onDidCreateFiles((e) => { Store.addSourceFiles(e.files); }); -workspace.onDidChangeTextDocument((e) => { - Store.bootstrap(); -}); - window.onDidChangeActiveTextEditor((e) => { Store.bootstrap(); }); @@ -87,6 +83,13 @@ const syncWithGit = () => { }; export async function activate(context: ExtensionContext): Promise { + workspace.onDidChangeTextDocument((e) => { + // Event is fired when logging to output channel + if (e.document.fileName.includes(context.extension.id)) return; + + Store.bootstrap(); + }); + workspace.onDidChangeConfiguration(async (e) => { const affected = e.affectsConfiguration(EXT_NAME); if (affected) { @@ -105,6 +108,7 @@ export async function activate(context: ExtensionContext): Promise { await syncTsPlugin(); } }); + const syncTsPlugin = async () => { const ext = extensions.getExtension("vscode.typescript-language-features"); if (ext) { @@ -188,9 +192,9 @@ export async function activate(context: ExtensionContext): Promise { context.subscriptions.push(_cssCodeLensProvider); context.subscriptions.push(_cssRenameSelectorProvider); } catch (e) { - console.error(e); + Store.outputChannel.error((e as Error).message); window.showWarningMessage( - "Something went wrong while activating React-TS-CSS extension" + "Something went wrong while activating React-TS-CSS extension. Check the output channel" ); } } diff --git a/src/parser/v2/css.ts b/src/parser/v2/css.ts index 8a91189..741f94d 100644 --- a/src/parser/v2/css.ts +++ b/src/parser/v2/css.ts @@ -31,6 +31,7 @@ import { isSibling, isSuffix, } from "../utils"; +import Store from "../../store/Store"; export const getLanguageService = (module: string) => { switch (path.extname(module) as CssModuleExtensions) { @@ -86,7 +87,9 @@ export const parseCss = async ( ); return { selectors, eofRange, variables, ast: ast as Stylesheet, colors }; } catch (e) { - console.error(e); + Store.outputChannel.error( + `CSSParserError: Parsing css module ${module} failed` + ); } }; diff --git a/src/providers/css/CSSProvider.ts b/src/providers/css/CSSProvider.ts index f988260..617f984 100644 --- a/src/providers/css/CSSProvider.ts +++ b/src/providers/css/CSSProvider.ts @@ -115,7 +115,13 @@ export class CSSProvider { }) ); } catch (e) { - console.error(e); + Store.outputChannel.error( + `CSSProviderError: Failed in document '${ + this.document.uri.fsPath + }' at '${this.position.line}:${this.position.character}' + Provider: ${this.providerKind} + ${e as Error}` + ); } return candidates; @@ -529,6 +535,9 @@ export class CSSDiagnosticsProvider extends CSSProvider { const references = await this.getReferences(); const filePath = normalizePath(this.document.uri.fsPath); const source_css_file = Store.cssModules.get(filePath); + if (!source_css_file) { + return []; + } const selectors = (await parseCss(source_css_file ?? ""))?.selectors; if (!selectors) return []; diff --git a/src/providers/css/codelens.ts b/src/providers/css/codelens.ts index 5779664..b4794b2 100644 --- a/src/providers/css/codelens.ts +++ b/src/providers/css/codelens.ts @@ -2,6 +2,7 @@ import * as vscode from "vscode"; import Settings from "../../settings"; import { ProviderKind } from "../types"; import { CSSCodeLensProvider, CSSProvider } from "./CSSProvider"; +import Store from "../../store/Store"; export class ReferenceCodeLens extends vscode.CodeLens { constructor( @@ -29,8 +30,8 @@ export class ReferenceCodeLensProvider implements vscode.CodeLensProvider { }); return provider.provideCodeLenses(); - } catch (e) { - console.error(e); + } catch (e: any) { + Store.outputChannel.error(`ReferenceCodeLensProviderError: ${e.message}`); return []; } } diff --git a/src/providers/css/rename-selector.ts b/src/providers/css/rename-selector.ts index 0fb8819..8e39b7a 100644 --- a/src/providers/css/rename-selector.ts +++ b/src/providers/css/rename-selector.ts @@ -12,6 +12,7 @@ import Settings from "../../settings"; import { CSSProvider, CSSRenameProvider } from "./CSSProvider"; import { ProviderKind } from "../types"; import { isSuffix, stripSelectHelpers } from "../../parser/utils"; +import Store from "../../store/Store"; export class RenameSelectorProvider implements RenameProvider { async provideRenameEdits( document: TextDocument, @@ -55,8 +56,11 @@ export class RenameSelectorProvider implements RenameProvider { }); const range = await provider.getSelectorRange(); return range; - } catch (e) { - console.error(e); + } catch (e: any) { + Store.outputChannel.error( + `RenameSelectorProviderError: Failed in document '${document}' at '${position.line}:${position.character}' + ${e.message}` + ); return; } } diff --git a/src/providers/ts/code-actions.ts b/src/providers/ts/code-actions.ts index 400b6e4..2306de8 100644 --- a/src/providers/ts/code-actions.ts +++ b/src/providers/ts/code-actions.ts @@ -118,7 +118,10 @@ export class DiagnosticCodeAction implements vscode.CodeActionProvider { action.diagnostics = [diagnostic]; return action; } catch (e) { - console.log(e); + Store.outputChannel.error( + e as Error, + `CodeActionError: Ingore warning failed` + ); throw e; } } diff --git a/src/providers/ts/completion.ts b/src/providers/ts/completion.ts index ecc2f1c..3202a43 100644 --- a/src/providers/ts/completion.ts +++ b/src/providers/ts/completion.ts @@ -15,6 +15,7 @@ import { import Settings from "../../settings"; import { ProviderKind } from "../types"; import { TSProvider } from "./TSProvider"; +import Store from "../../store/Store"; export class SelectorsCompletionProvider implements CompletionItemProvider { async provideCompletionItems( @@ -89,7 +90,10 @@ export class SelectorsCompletionProvider implements CompletionItemProvider { } } catch (e) {} } catch (e) { - console.info(e); + Store.outputChannel.error( + `${e as Error}`, + `SelectorCompletionProvider: Failed in document '${document}' at '${position.line}:${position.character}'` + ); } return []; } @@ -115,8 +119,11 @@ export class ImportCompletionProvider implements CompletionItemProvider { additionalTextEdits: c.additionalEdits, })) ); - } catch (e) { - console.error(e); + } catch (e: any) { + Store.outputChannel.error( + `ImportCompletionProvider: Failed in document '${document}' at '${position.line}:${position.character}' + ${e.message}` + ); return; } } diff --git a/src/providers/ts/definitions.ts b/src/providers/ts/definitions.ts index d1380b5..176ee72 100644 --- a/src/providers/ts/definitions.ts +++ b/src/providers/ts/definitions.ts @@ -11,6 +11,7 @@ import { import Settings from "../../settings"; import { TSProvider } from "./TSProvider"; import { ProviderKind } from "./../types"; +import Store from "../../store/Store"; export class DefnitionProvider implements vscode_DefinitionProvider { async provideDefinition( @@ -49,8 +50,11 @@ export class DefnitionProvider implements vscode_DefinitionProvider { return [locationLinks]; } return []; - } catch (e) { - console.error(e); + } catch (e: any) { + Store.outputChannel.error( + `TSDefinitionProvider: Failed in document '${document}' at '${position.line}:${position.character}' + ${e.message}` + ); return []; } } diff --git a/src/providers/ts/hover.ts b/src/providers/ts/hover.ts index 1b17bdc..fcf1321 100644 --- a/src/providers/ts/hover.ts +++ b/src/providers/ts/hover.ts @@ -48,8 +48,11 @@ export class HoverProvider implements vscode_HoverProvider { return hover; } return; - } catch (e) { - console.error(e); + } catch (e: any) { + Store.outputChannel.error( + `TSHoverProvider: Failed in document '${document}' at '${position.line}:${position.character}' + ${e.message}` + ); return; } } diff --git a/src/store/Store.ts b/src/store/Store.ts index 030ab75..a16af11 100644 --- a/src/store/Store.ts +++ b/src/store/Store.ts @@ -11,6 +11,7 @@ import Settings from "../settings"; import { Parser } from "../parser/Parser"; import { DiagnosticsProvider } from "../providers/diagnostics"; import { normalizePath } from "../path-utils"; +import * as JSON5 from "json5/lib"; // Full file path of the active opened file type CssModules = Map; @@ -44,6 +45,9 @@ export class Store { public tsJsConfig: TsJsConfigMap = new Map(); public tsModules: TsModules = new Map(); public parser: Parser | undefined; + public outputChannel = window.createOutputChannel("React CSS Modules", { + log: true, + }); constructor() { const uri = window.activeTextEditor?.document?.uri; @@ -166,19 +170,27 @@ export class Store { const _path = path.resolve(this.workSpaceRoot ?? "", config); const contents = (await fs_promises.readFile(_path)).toString(); try { + const tsConfig = JSON5.parse(contents); this.tsJsConfig.set(_path, { - ...JSON.parse(contents), + ...tsConfig, baseDir: normalizePath( path.join(this.workSpaceRoot ?? "", path.dirname(config)) ), } as TsJsConfig); - } catch (e) { - // console.error(e); + } catch (e: any) { + this.outputChannel.error( + `TsJsConfigSyntaxError: Failed to parse config at ${_path} + ${e.message}` + ); } }) ); - } catch (e) { + } catch (e: any) { // Catch errors here + this.outputChannel.error( + `TsJsConfigSyntaxError: Failed to parse config + ${e.message}` + ); } } @@ -208,13 +220,15 @@ export class Store { const alias = normalizePath(path.dirname(source)); const module_name = path.basename(source); const paths = config.compilerOptions.paths; + const dir = (paths?.[alias] ?? [""]).join(""); const baseUrl = config.compilerOptions.baseUrl; if (baseUrl) { const final_path = normalizePath( path.join( config.baseDir, config.compilerOptions.baseUrl ?? "", - source + !!alias.match(/^\@/g)?.[0] ? dir.replace("*", "") : alias, + module_name ) ); if (this.cssModules.has(final_path)) { diff --git a/src/test/suite/extension.test.ts b/src/test/suite/extension.test.ts index 6c25c44..f94b486 100644 --- a/src/test/suite/extension.test.ts +++ b/src/test/suite/extension.test.ts @@ -820,6 +820,14 @@ suite("TS Config path aliases", async () => { path.join(__dirname, examplesLocation, "react-app/src/App.tsx") ); + const AliasedModuleComponent = Uri.file( + path.join( + __dirname, + examplesLocation, + "react-app/src/test/aliased-modules/AliasedModules.tsx" + ) + ); + test("should not report an import diagnostics error on aliased module imports", async () => { const document = await workspace.openTextDocument(AppComponent); await window.showTextDocument(document); @@ -853,6 +861,36 @@ suite("TS Config path aliases", async () => { assert.notEqual(result, undefined); StorageInstance.flushStorage(); }); + + test("should resolve `@` scoped modules when base url is also defined:Hover", async () => { + const document = await workspace.openTextDocument( + AliasedModuleComponent + ); + await window.showTextDocument(document); + + await StorageInstance.bootstrap(); + const hover = new HoverProvider(); + const position = new Position(5, 31); + const result = await hover.provideHover(document, position); + + assert.notEqual(result, undefined); + StorageInstance.flushStorage(); + }); + + test("should resolve `@` scoped modules when base url is also defined:Definition", async () => { + const document = await workspace.openTextDocument( + AliasedModuleComponent + ); + await window.showTextDocument(document); + + await StorageInstance.bootstrap(); + const def = new DefnitionProvider(); + const position = new Position(6, 29); + const result = await def.provideDefinition(document, position); + + assert.notEqual(result, undefined); + StorageInstance.flushStorage(); + }); } ); });