From 874e8b8d48ab0fd30496eecbac4be326f073de10 Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Thu, 2 Jun 2022 12:33:46 +0530 Subject: [PATCH] change separator char to `:` (#1) * add error message styling fns * adds functions apply consistent styling for classnames and modules in error messages * replace '#' to ':' in import source regex * update tests to replace separator to ':' * update docs to reflect new separator * remove refs to old sep --- README.md | 14 ++++---- src/index.ts | 2 +- src/transforms.ts | 2 +- src/utils.ts | 33 ++++++++++++------ tests/transforms.test.ts | 73 +++++++++++++++++++++++----------------- tests/utils.test.ts | 25 +++++++++----- 6 files changed, 91 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index aaf630a..ba5389f 100644 --- a/README.md +++ b/README.md @@ -98,13 +98,13 @@ function Component() { **By default, If plugin found any `'*.module.css'` import, it will transform all our css classes to use style objects.** -If we want to use global css classes, we need to add `'#'` at the end of the class. This will tell plugin not to transform these classes and keep them as is: +If we want to use global css classes, we need to add `':g'` at the end of the class. This will tell plugin not to transform these classes and keep them as is: ```jsx import "./m1.module.css" function Component() { - return

....

+ return

....

} ``` @@ -117,7 +117,7 @@ function Component() { } ``` -In this example, `'bar'` might be coming from our global stylesheet while `'foo'` and `'baz'` are scoped to the imported module. +In this example, `'bar'` might be declared in the global stylesheet while `'foo'` and `'baz'` are scoped to the imported module. *The transformed code will use object indexing instead of dot-notation, this helps us to use dashes within our class names (eg. `className="foo-bar baz"`) or else, we would have to use camel-case pattern while using css classes.* @@ -163,13 +163,13 @@ eg. we can import two module and use them like: ```jsx // original -import "./layout.module.css#layout" -import "./component.module.css#comp" +import "./layout.module.css:layout" +import "./component.module.css:comp" function Component() { return ( -
-

...

+
+

...

) } diff --git a/src/index.ts b/src/index.ts index 60cde95..08829c4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,7 +18,7 @@ const API = function ({ types: t }: typeof babel): PluginObj { // stores previous css-modules found on the same file let modules: Modules = state.pluginState.modules ?? { namedModules: {} } - if (!t.isImportDeclaration(path.node) || !/.module.(s[ac]ss|css)(#.*)?$/iu.test(path.node.source.value)) return + if (!t.isImportDeclaration(path.node) || !/.module.(s[ac]ss|css)(:.*)?$/iu.test(path.node.source.value)) return // saving path for error messages CSSModuleError.path = path diff --git a/src/transforms.ts b/src/transforms.ts index 4d18b79..2c23b83 100644 --- a/src/transforms.ts +++ b/src/transforms.ts @@ -110,7 +110,7 @@ export const getImportInfo = (statement: t.ImportDeclaration): DefaultModule | M } } - // eg. import "./moduleA.module.css#m1" + // eg. import "./moduleA.module.css:m1" return { moduleSource: module.moduleSource, moduleName: module.moduleName, diff --git a/src/utils.ts b/src/utils.ts index ec7a88d..5d4fc87 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,7 +2,7 @@ import { NodePath } from "@babel/core" import chalk from "chalk" /** - * splits full classname (with '#') into classname and module name + * splits full classname (with ':') into classname and module name * * @param classname full classname with module * @param defaultModule default module for the file @@ -10,22 +10,25 @@ import chalk from "chalk" */ export const splitClsName = (classname: string, defaultModule: string): { classname: string; module?: string } => { if (shouldTransform(classname)) { - // TODO: throw error if more than one '#' is present - let [splittedClassName, module] = classname.split("#") + // TODO: throw error if more than one sep is present, or use last sep in the classname to split + let [splittedClassName, module] = classname.split(":") + if (module === "") { + throw new CSSModuleError(`no module name found after ':' on ${CSSModuleError.cls(classname)}`) + } return { classname: splittedClassName.trim(), module: module || defaultModule, } } else { + // global class return { - // global class - classname: classname.slice(0, classname.length - 1), + classname: classname.slice(0, classname.length - 2), } } } export const shouldTransform = (classname: string) => { - return !classname.endsWith("#") + return !classname.endsWith(":g") } export const splitClassnames = (classes: string) => { @@ -33,25 +36,35 @@ export const splitClassnames = (classes: string) => { } export const splitModuleSource = (source: string): { moduleSource: string; moduleName?: string } => { - if (!source.includes("#")) { + if (!source.includes(":")) { return { moduleSource: source, } } - let [moduleSource, moduleName] = source.split("#") + let [moduleSource, moduleName] = source.split(":") return { moduleSource, moduleName } } export class CSSModuleError extends Error { errorMessage: string - static path: NodePath + static path: NodePath | undefined constructor(errorMessage: string) { super() this.errorMessage = errorMessage this.name = chalk.red("CSSModuleError") - this.message = `at (${CSSModuleError.path.node.loc?.start.line}:${CSSModuleError.path.node.loc?.start.column}) + this.message = `at (${CSSModuleError.path?.node.loc?.start.line}:${ + CSSModuleError.path?.node.loc?.start.column + }): ${this.errorMessage.replace(/ +/g, " ")} `.replace(/ +/g, " ") } + + static cls(cls: string) { + return `'${chalk.cyan(cls)}'` + } + + static mod(mod: string) { + return `'${chalk.cyan(mod)}'` + } } diff --git a/tests/transforms.test.ts b/tests/transforms.test.ts index eb475c9..8a8e1cc 100644 --- a/tests/transforms.test.ts +++ b/tests/transforms.test.ts @@ -31,21 +31,21 @@ describe("single imports", () => { }) test("with specifier (ignore names)", async () => { - let source = `import style from "./foo.module.scss#m1"` + let source = `import style from "./foo.module.scss:m1"` let code = await runWithBabel(source) expect(code).toMatchInlineSnapshot(`"import style from \\"./foo.module.scss\\";"`) - source = `import style from "./foo.module.css#m1"` + source = `import style from "./foo.module.css:m1"` code = await runWithBabel(source) expect(code).toMatchInlineSnapshot(`"import style from \\"./foo.module.css\\";"`) }) test("with named-module", async () => { - let source = `import "./foo.module.scss#m1"` + let source = `import "./foo.module.scss:m1"` let code = await runWithBabel(source) expect(code).toMatchInlineSnapshot(`"import _m from \\"./foo.module.scss\\";"`) - source = `import "./foo.module.css#m1"` + source = `import "./foo.module.css:m1"` code = await runWithBabel(source) expect(code).toMatchInlineSnapshot(`"import _m from \\"./foo.module.css\\";"`) }) @@ -72,8 +72,8 @@ describe("imports multiple module", () => { test("with named-modules", async () => { let source = ` - import "./module1.module.css#m1" - import "./module2.module.css#m2" + import "./module1.module.css:m1" + import "./module2.module.css:m2" ` let code = await runWithBabel(source) expect(code).toMatchInlineSnapshot(` @@ -84,16 +84,16 @@ import _m2 from \\"./module2.module.css\\";" test("with same named-modules twice", async () => { let source = ` - import "./module1.module.css#m1" - import "./module2.module.css#m1" + import "./module1.module.css:m1" + import "./module2.module.css:m1" ` await expect(runWithBabel(source)).rejects.toThrow(CSSModuleError) }) test("with specifier", async () => { let source = ` - import style from "./module1.module.css#m1" - import style1 from "./module2.module.css#m2" + import style from "./module1.module.css:m1" + import style1 from "./module2.module.css:m2" ` let code = await runWithBabel(source) expect(code).toMatchInlineSnapshot(` @@ -104,8 +104,8 @@ import style1 from \\"./module2.module.css\\";" test("with same specifier twice", async () => { let source = ` - import style from "./module1.module.css#m1" - import style from "./module2.module.css#m2" + import style from "./module1.module.css:m1" + import style from "./module2.module.css:m2" ` expect(runWithBabel(source)).rejects.toThrow(SyntaxError) }) @@ -115,7 +115,7 @@ describe("different kinds together", () => { test("each kind once", async () => { let source = ` import style from "./module1.module.css" - import "./module2.module.css#m2" + import "./module2.module.css:m2" import "./module3.module.css" ` let code = await runWithBabel(source) @@ -130,7 +130,7 @@ import _style from \\"./module3.module.css\\";" let source = ` import "./component.module.css" import layout from "./layout.module.css" - import "./layout2.module.css#layout" + import "./layout2.module.css:layout" ` await expect(runWithBabel(source)).rejects.toThrow(CSSModuleError) }) @@ -138,7 +138,7 @@ import _style from \\"./module3.module.css\\";" test("same module name used twice on different kinds (2)", async () => { let source = ` import "./component.module.css" - import "./layout2.module.css#layout" + import "./layout2.module.css:layout" import layout from "./layout.module.css" ` await expect(runWithBabel(source)).rejects.toThrow(CSSModuleError) @@ -184,10 +184,10 @@ function Component() { test("with named-module", async () => { let source = ` - import "./component.module.css#m1" + import "./component.module.css:m1" function Component() { - return

+ return

} ` let code = await runWithBabel(source) @@ -199,6 +199,17 @@ function Component() { }" `) }) + + test("named module class with colon only", async () => { + let source = ` + import "./component.module.css" + + function Component() { + return

+ } + ` + await expect(() => runWithBabel(source)).rejects.toThrow(CSSModuleError) + }) }) describe("jsx with multiple modules", () => { @@ -228,7 +239,7 @@ function component() { import layout from "./layout.module.css" function Component() { - return

+ return

} ` let code = await runWithBabel(source) @@ -244,11 +255,11 @@ function Component() { test("named-module", async () => { let source = ` - import "./component.module.css#style" - import "./layout.module.css#layout" + import "./component.module.css:style" + import "./layout.module.css:layout" function Component() { - return

+ return

} ` let code = await runWithBabel(source) @@ -268,7 +279,7 @@ function Component() { import layout from "./layout.module.css" function Component() { - return

+ return

} ` await expect(runWithBabel(source)).rejects.toThrow(CSSModuleError) @@ -276,11 +287,11 @@ function Component() { test("named-module (class uses non-existent module)", async () => { let source = ` - import "./component.module.css#style" - import "./layout.module.css#layout" + import "./component.module.css:style" + import "./layout.module.css:layout" function component() { - return

+ return

} ` await expect(runWithBabel(source)).rejects.toThrow(CSSModuleError) @@ -292,12 +303,12 @@ describe("jsx with multiple kinds of module", () => { let source = ` import style from "./component.module.css" import layout from "./layout.module.css" - import "./layout2.module.css#altLayout" + import "./layout2.module.css:altLayout" function Component() { return ( -
-

+
+

) } @@ -320,12 +331,12 @@ function Component() { let source = ` import style from "./component.module.css" import layout from "./layout.module.css" - import "./layout2.module.css#altLayout" + import "./layout2.module.css:altLayout" function Component() { return ( -
-

+
+

) } diff --git a/tests/utils.test.ts b/tests/utils.test.ts index 4204c65..07faeb5 100644 --- a/tests/utils.test.ts +++ b/tests/utils.test.ts @@ -1,31 +1,35 @@ import * as t from "@babel/types" -import { splitClsName, shouldTransform } from "../src/utils" +import { splitClsName, shouldTransform, CSSModuleError } from "../src/utils" import { createModuleMemberExpression } from "../src/transforms" // testing splitModuleName describe("split string into module and classnames", () => { test("class without modifier", () => { - let { classname, module } = splitClsName("classB#m1", "m1") + let { classname, module } = splitClsName("classB:m1", "m1") expect(classname).toBe("classB") expect(module).toBe("m1") }) test("class with modifier", () => { - let { classname, module } = splitClsName("classB-modifier#m2", "m1") + let { classname, module } = splitClsName("classB-modifier:m2", "m1") expect(classname).toBe("classB-modifier") expect(module).toBe("m2") }) - test("using global module", () => { - let { classname, module } = splitClsName("classA#", "m1") + test("global module", () => { + let { classname, module } = splitClsName("classA:g", "m1") expect(classname).toBe("classA") expect(module).toBeUndefined() }) + test("classname with separator only", () => { + expect(() => splitClsName("classA:", "m1")).toThrow(CSSModuleError) + }) + test("use default module", () => { let { classname, module } = splitClsName("classA", "m1") @@ -36,13 +40,18 @@ describe("split string into module and classnames", () => { // testing checkShouldTransform describe("transform are applied correctly", () => { - test("transform needed", () => { + test("should transform", () => { let transform = shouldTransform("foo-bar") expect(transform).toBe(true) }) - test("transform not needed", () => { - let transform = shouldTransform("foo-bar#") + test("should transform (only sep)", () => { + let transform = shouldTransform("foo-bar:") + expect(transform).toBe(true) + }) + + test("should not transform", () => { + let transform = shouldTransform("foo-bar:g") expect(transform).toBe(false) }) })