diff --git a/package-lock.json b/package-lock.json index e458b1b4..12b4dd76 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,6 @@ "version": "5.2.4", "license": "MIT", "dependencies": { - "camelcase": "^6.2.0", "icss-utils": "^5.1.0", "loader-utils": "^2.0.0", "postcss": "^8.2.15", @@ -4062,6 +4061,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true, "engines": { "node": ">=10" }, @@ -22320,7 +22320,8 @@ "camelcase": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==" + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true }, "camelcase-keys": { "version": "6.2.2", diff --git a/package.json b/package.json index 471f9d26..ed379d02 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,6 @@ "webpack": "^4.27.0 || ^5.0.0" }, "dependencies": { - "camelcase": "^6.2.0", "icss-utils": "^5.1.0", "loader-utils": "^2.0.0", "postcss": "^8.2.15", diff --git a/src/utils.js b/src/utils.js index b2c27c8e..746c0650 100644 --- a/src/utils.js +++ b/src/utils.js @@ -10,7 +10,6 @@ import modulesValues from "postcss-modules-values"; import localByDefault from "postcss-modules-local-by-default"; import extractImports from "postcss-modules-extract-imports"; import modulesScope from "postcss-modules-scope"; -import camelCase from "camelcase"; const WEBPACK_IGNORE_COMMENT_REGEXP = /webpackIgnore:(\s+)?(true|false)/; @@ -19,6 +18,68 @@ const regexSingleEscape = /[ -,.\/:-@[\]\^`{-~]/; const regexExcessiveSpaces = /(^|\\+)?(\\[A-F0-9]{1,6})\x20(?![a-fA-F0-9\x20])/g; +const preserveCamelCase = (string) => { + let result = string; + let isLastCharLower = false; + let isLastCharUpper = false; + let isLastLastCharUpper = false; + + for (let i = 0; i < result.length; i++) { + const character = result[i]; + + if (isLastCharLower && /[\p{Lu}]/u.test(character)) { + result = `${result.slice(0, i)}-${result.slice(i)}`; + isLastCharLower = false; + isLastLastCharUpper = isLastCharUpper; + isLastCharUpper = true; + i += 1; + } else if ( + isLastCharUpper && + isLastLastCharUpper && + /[\p{Ll}]/u.test(character) + ) { + result = `${result.slice(0, i - 1)}-${result.slice(i - 1)}`; + isLastLastCharUpper = isLastCharUpper; + isLastCharUpper = false; + isLastCharLower = true; + } else { + isLastCharLower = + character.toLowerCase() === character && + character.toUpperCase() !== character; + isLastLastCharUpper = isLastCharUpper; + isLastCharUpper = + character.toUpperCase() === character && + character.toLowerCase() !== character; + } + } + + return result; +}; + +function camelCase(input) { + let result = input.trim(); + + if (result.length === 0) { + return ""; + } + + if (result.length === 1) { + return result.toLowerCase(); + } + + const hasUpperCase = result !== result.toLowerCase(); + + if (hasUpperCase) { + result = preserveCamelCase(result); + } + + return result + .replace(/^[_.\- ]+/, "") + .toLowerCase() + .replace(/[_.\- ]+([\p{Alpha}\p{N}_]|$)/gu, (_, p1) => p1.toUpperCase()) + .replace(/\d+([\p{Alpha}\p{N}_]|$)/gu, (m) => m.toUpperCase()); +} + function escape(string) { let output = ""; let counter = 0; @@ -881,4 +942,5 @@ export { sort, WEBPACK_IGNORE_COMMENT_REGEXP, combineRequests, + camelCase, }; diff --git a/test/__snapshots__/camelCase.test.js.snap b/test/__snapshots__/camelCase.test.js.snap new file mode 100644 index 00000000..0c42185c --- /dev/null +++ b/test/__snapshots__/camelCase.test.js.snap @@ -0,0 +1,63 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`camelCase should transform 1`] = `""`; + +exports[`camelCase should transform: foo bar 1`] = `"fooBar"`; + +exports[`camelCase should transform: __foo__bar__ 1`] = `"fooBar"`; + +exports[`camelCase should transform: - 1`] = `"-"`; + +exports[`camelCase should transform: --__--_--_ 1`] = `""`; + +exports[`camelCase should transform: --foo..bar 1`] = `"fooBar"`; + +exports[`camelCase should transform: --foo---bar 1`] = `"fooBar"`; + +exports[`camelCase should transform: --foo---bar-- 1`] = `"fooBar"`; + +exports[`camelCase should transform: --foo--1 1`] = `"foo1"`; + +exports[`camelCase should transform: --foo-bar 1`] = `"fooBar"`; + +exports[`camelCase should transform: 1Hello 1`] = `"1Hello"`; + +exports[`camelCase should transform: A::a 1`] = `"a::a"`; + +exports[`camelCase should transform: F 1`] = `"f"`; + +exports[`camelCase should transform: FOO-BAR 1`] = `"fooBar"`; + +exports[`camelCase should transform: FOÈ-BAR 1`] = `"foèBar"`; + +exports[`camelCase should transform: FOÈ-BAr 1`] = `"foèBAr"`; + +exports[`camelCase should transform: Hello1World11foo 1`] = `"hello1World11Foo"`; + +exports[`camelCase should transform: foo 1`] = `"foo"`; + +exports[`camelCase should transform: foo bar 1`] = `"fooBar"`; + +exports[`camelCase should transform: foo bar! 1`] = `"fooBar!"`; + +exports[`camelCase should transform: foo bar# 1`] = `"fooBar#"`; + +exports[`camelCase should transform: foo bar? 1`] = `"fooBar?"`; + +exports[`camelCase should transform: foo_bar 1`] = `"fooBar"`; + +exports[`camelCase should transform: foo--bar 1`] = `"fooBar"`; + +exports[`camelCase should transform: foo-bar 1`] = `"fooBar"`; + +exports[`camelCase should transform: foo-bar-baz 1`] = `"fooBarBaz"`; + +exports[`camelCase should transform: fooBar 1`] = `"fooBar"`; + +exports[`camelCase should transform: fooBar-baz 1`] = `"fooBarBaz"`; + +exports[`camelCase should transform: fooBarBaz-bazzy 1`] = `"fooBarBazBazzy"`; + +exports[`camelCase should transform: h2w 1`] = `"h2W"`; + +exports[`camelCase should transform: mGridCol6@md 1`] = `"mGridCol6@md"`; diff --git a/test/camelCase.test.js b/test/camelCase.test.js new file mode 100644 index 00000000..4e2fe077 --- /dev/null +++ b/test/camelCase.test.js @@ -0,0 +1,43 @@ +import { camelCase } from "../src/utils"; + +describe("camelCase", () => { + const data = [ + "foo", + "foo-bar", + "foo-bar-baz", + "foo--bar", + "--foo-bar", + "--foo---bar", + "FOO-BAR", + "FOÈ-BAR", + "FOÈ-BAr", + "--foo---bar--", + "--foo--1", + "--foo..bar", + "foo_bar", + "__foo__bar__", + "foo bar", + " foo bar ", + "-", + "fooBar", + "fooBar-baz", + "fooBarBaz-bazzy", + "", + "--__--_--_", + "A::a", + "1Hello", + "h2w", + "F", + "foo bar?", + "foo bar!", + "foo bar#", + "mGridCol6@md", + "Hello1World11foo", + ]; + + for (const entry of data) { + it(`should transform`, () => { + expect(camelCase(entry)).toMatchSnapshot(`${entry}`); + }); + } +});