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();
+ });
}
);
});