diff --git a/.prettierrc.yaml b/.prettierrc.yaml index d9cd90e3..7804e493 100644 --- a/.prettierrc.yaml +++ b/.prettierrc.yaml @@ -1,5 +1,5 @@ --- arrowParens: avoid -printWidth: 120 +printWidth: 100 singleQuote: true trailingComma: all diff --git a/package.json b/package.json index 6d2a0029..88b91387 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "scripts": { "build": "tsc", "clean": "rimraf dist/*", + "console": "ts-node", "eslint": "eslint --ext .js,.ts src", "eslint:fix": "eslint --fix --ext .js,.ts src", "prepack": "npm-run-all eslint test clean build", @@ -38,6 +39,7 @@ "prettier": "^2.0.5", "rimraf": "^3.0.2", "ts-jest": "^26.0.0", + "ts-node": "^8.10.2", "typescript": "^3.9.3" }, "peerDependencies": { diff --git a/src/rules/naming-convention.ts b/src/rules/naming-convention.ts index b42d2df0..26c73914 100644 --- a/src/rules/naming-convention.ts +++ b/src/rules/naming-convention.ts @@ -51,8 +51,10 @@ export const namingConvention: Rule.RuleModule = { const suggest = (() => { if (!presetCaseConverters[rule]) return null; - const name = [presetCaseConverters[rule](filename), ...rest].join('.'); - return ` Should rename to ${name}`; + const alterName = presetCaseConverters[rule](filename) ?? ''; + if (!ruleRegExp.test(alterName)) return null; + + return ` Should rename to ${[alterName, ...rest].join('.')}.`; })(); const message = `The filename must follow the rule: '${rule}'.${suggest ?? ''}`; diff --git a/src/utils/preset-case-converters.ts b/src/utils/preset-case-converters.ts index 0736bb76..b4903dea 100644 --- a/src/utils/preset-case-converters.ts +++ b/src/utils/preset-case-converters.ts @@ -1,32 +1,18 @@ -const toCamelCase = (name: string) => - name - // for PascalCase - .replace(/^[A-Z]/, match => match.toLowerCase()) - // for kebab-case and snake_case - .replace(/[-_]([a-z])/, (_, p1) => p1.toUpperCase()); +// Replace char at beginning of name +const camel2Pascal = (name: string) => name.replace(/^[a-z]/, match => match.toUpperCase()); +const pascal2Camel = (name: string) => name.replace(/^[A-Z]/, match => match.toLowerCase()); -const toKebabCase = (name: string) => - name - // for camelCase and PascalCase - .replace(/([a-z0-9])([A-Z])|([A-Z])([A-Z])(?:[a-z])/, '$1-$2') - .toLowerCase() - // for snake_case - .replace('_', '-'); +// Replace all hyphen and underscore +const kebab2Snake = (name: string) => name.replace(/-/g, '_'); +const snake2Kebab = (name: string) => name.replace(/_/g, '-'); +const kebabAndSnake2CamelAndPascal = (name: string) => + name.replace(/[-_]([a-z])/g, (_, p1) => p1.toUpperCase()); -const toPascalCase = (name: string) => - name - // for camelCase, kebab-case and snake_case - .replace(/^[a-z]/, match => match.toUpperCase()) - // for kebab-case and snake_case - .replace(/[-_]([a-z])/, (_, p1) => p1.toUpperCase()); - -const toSnakeCase = (name: string) => - name - // for camelCase and PascalCase - .replace(/([a-z0-9])([A-Z])|([A-Z])([A-Z])(?:[a-z])/, '$1_$2') - .toLowerCase() - // for kebab-case - .replace('-', '_'); +// Replace all uppercase chars with hyphen or underscore, and lowercase +const camelAndPascal2Kebab = (name: string) => + name.replace(/([a-z0-9])([A-Z])|([A-Z])([A-Z])(?:[a-z])/g, '$1-$2').toLowerCase(); +const camelAndPascal2Snake = (name: string) => + name.replace(/([a-z0-9])([A-Z])|([A-Z])([A-Z])(?:[a-z])/g, '$1_$2').toLowerCase(); type PresetCaseConverters = { camelCase: (name: string) => string; @@ -37,8 +23,8 @@ type PresetCaseConverters = { }; export const presetCaseConverters: PresetCaseConverters = { - camelCase: toCamelCase, - 'kebab-case': toKebabCase, - PascalCase: toPascalCase, - snake_case: toSnakeCase, + camelCase: name => kebabAndSnake2CamelAndPascal(pascal2Camel(name)), + 'kebab-case': name => snake2Kebab(camelAndPascal2Kebab(name)), + PascalCase: name => kebabAndSnake2CamelAndPascal(camel2Pascal(name)), + snake_case: name => kebab2Snake(camelAndPascal2Snake(name)), }; diff --git a/tests/rules/naming-convention.test.ts b/tests/rules/naming-convention.test.ts index 7b253053..95f4665c 100644 --- a/tests/rules/naming-convention.test.ts +++ b/tests/rules/naming-convention.test.ts @@ -13,7 +13,12 @@ ruleTester.run('naming-convention', namingConvention, { { code: '', filename: 'camelCase.js', - errors: ["The filename must follow the rule: 'kebab-case'. Should rename to camel-case.js"], + errors: ["The filename must follow the rule: 'kebab-case'. Should rename to camel-case.js."], + }, + { + code: '', + filename: '00001_chaos-Name.js', + errors: ["The filename must follow the rule: 'kebab-case'."], }, { code: '', diff --git a/tests/utils/preset-case-converters.test.ts b/tests/utils/preset-case-converters.test.ts index 3add7dae..5abfc8c5 100644 --- a/tests/utils/preset-case-converters.test.ts +++ b/tests/utils/preset-case-converters.test.ts @@ -14,15 +14,30 @@ describe('PresetConverter of camelCase', () => { }); test('should return camelized strings when name is kebab-case', () => { - expect(targetNames.kebabCase.map(subject)).toEqual(['kebabCase', 'kebab0Case', 'kebabCase0']); + expect(targetNames.kebabCase.map(subject)).toEqual([ + 'kebabCase', + 'threeOrMoreWordsIncludingKebabCase', + 'kebab0Case', + 'kebabCase0', + ]); }); test('should return camelized strings when name is PascalCase', () => { - expect(targetNames.pascalCase.map(subject)).toEqual(['pascalCase', 'pascal0Case', 'pascalCase0']); + expect(targetNames.pascalCase.map(subject)).toEqual([ + 'pascalCase', + 'threeOrMoreWordsIncludingPascalCase', + 'pascal0Case', + 'pascalCase0', + ]); }); test('should return camelized strings when name is snake_case', () => { - expect(targetNames.snakeCase.map(subject)).toEqual(['snakeCase', 'snake0Case', 'snakeCase0']); + expect(targetNames.snakeCase.map(subject)).toEqual([ + 'snakeCase', + 'threeOrMoreWordsIncludingSnakeCase', + 'snake0Case', + 'snakeCase0', + ]); }); }); @@ -34,7 +49,12 @@ describe('PresetConverter of kebab-case', () => { }); test('should return hyphenized strings when name is camelCase', () => { - expect(targetNames.camelCase.map(subject)).toEqual(['camel-case', 'camel0-case', 'camel-case0']); + expect(targetNames.camelCase.map(subject)).toEqual([ + 'camel-case', + 'three-or-more-words-including-camel-case', + 'camel0-case', + 'camel-case0', + ]); }); test('should return same strings when name is kebab-case', () => { @@ -42,11 +62,21 @@ describe('PresetConverter of kebab-case', () => { }); test('should return hyphenized strings when name is PascalCase', () => { - expect(targetNames.pascalCase.map(subject)).toEqual(['pascal-case', 'pascal0-case', 'pascal-case0']); + expect(targetNames.pascalCase.map(subject)).toEqual([ + 'pascal-case', + 'three-or-more-words-including-pascal-case', + 'pascal0-case', + 'pascal-case0', + ]); }); test('should return hyphenized strings when name is snake_case', () => { - expect(targetNames.snakeCase.map(subject)).toEqual(['snake-case', 'snake0-case', 'snake-case0']); + expect(targetNames.snakeCase.map(subject)).toEqual([ + 'snake-case', + 'three-or-more-words-including-snake-case', + 'snake0-case', + 'snake-case0', + ]); }); }); @@ -54,15 +84,30 @@ describe('PresetConverter of PascalCase', () => { const subject = (name: string) => presetCaseConverters.PascalCase(name); test('should return pascalized strings when name is narmalcase', () => { - expect(targetNames.normalCase.map(subject)).toEqual(['Normalcase', 'Normal0case', 'Normalcase0']); + expect(targetNames.normalCase.map(subject)).toEqual([ + 'Normalcase', + 'Threeormorewordsincludingnormalcase', + 'Normal0case', + 'Normalcase0', + ]); }); test('should return pascalized strings when name is camelCase', () => { - expect(targetNames.camelCase.map(subject)).toEqual(['CamelCase', 'Camel0Case', 'CamelCase0']); + expect(targetNames.camelCase.map(subject)).toEqual([ + 'CamelCase', + 'ThreeOrMoreWordsIncludingCamelCase', + 'Camel0Case', + 'CamelCase0', + ]); }); test('should return pascalized strings when name is kebab-case', () => { - expect(targetNames.kebabCase.map(subject)).toEqual(['KebabCase', 'Kebab0Case', 'KebabCase0']); + expect(targetNames.kebabCase.map(subject)).toEqual([ + 'KebabCase', + 'ThreeOrMoreWordsIncludingKebabCase', + 'Kebab0Case', + 'KebabCase0', + ]); }); test('should return same strings when name is PascalCase', () => { @@ -70,7 +115,12 @@ describe('PresetConverter of PascalCase', () => { }); test('should return pascalized strings when name is snake_case', () => { - expect(targetNames.snakeCase.map(subject)).toEqual(['SnakeCase', 'Snake0Case', 'SnakeCase0']); + expect(targetNames.snakeCase.map(subject)).toEqual([ + 'SnakeCase', + 'ThreeOrMoreWordsIncludingSnakeCase', + 'Snake0Case', + 'SnakeCase0', + ]); }); }); @@ -82,15 +132,30 @@ describe('PresetConverter of snake_case', () => { }); test('should return underscorized strings when name is camelCase', () => { - expect(targetNames.camelCase.map(subject)).toEqual(['camel_case', 'camel0_case', 'camel_case0']); + expect(targetNames.camelCase.map(subject)).toEqual([ + 'camel_case', + 'three_or_more_words_including_camel_case', + 'camel0_case', + 'camel_case0', + ]); }); test('should return underscorized strings when name is kebab-case', () => { - expect(targetNames.kebabCase.map(subject)).toEqual(['kebab_case', 'kebab0_case', 'kebab_case0']); + expect(targetNames.kebabCase.map(subject)).toEqual([ + 'kebab_case', + 'three_or_more_words_including_kebab_case', + 'kebab0_case', + 'kebab_case0', + ]); }); test('should return underscorized strings when name is PascalCase', () => { - expect(targetNames.pascalCase.map(subject)).toEqual(['pascal_case', 'pascal0_case', 'pascal_case0']); + expect(targetNames.pascalCase.map(subject)).toEqual([ + 'pascal_case', + 'three_or_more_words_including_pascal_case', + 'pascal0_case', + 'pascal_case0', + ]); }); test('should return same strings when name is snake_case', () => { diff --git a/tests/utils/preset-cases.test.ts b/tests/utils/preset-cases.test.ts index 31dabc82..04e72ab3 100644 --- a/tests/utils/preset-cases.test.ts +++ b/tests/utils/preset-cases.test.ts @@ -6,23 +6,27 @@ describe('Preset RegExp of camelCase', () => { const subject = (name: string) => presetCases.camelCase.test(name); test('should return true when name is narmalcase', () => { - expect(targetNames.normalCase.map(subject)).toEqual([true, true, true]); + expect(targetNames.normalCase.map(subject).every(n => n)).toBeTruthy(); }); test('should return true when name is camelCase', () => { - expect(targetNames.camelCase.map(subject)).toEqual([true, true, true]); + expect(targetNames.camelCase.map(subject).every(n => n)).toBeTruthy(); }); test('should return false when name is kebab-case', () => { - expect(targetNames.kebabCase.map(subject)).toEqual([false, false, false]); + expect(targetNames.kebabCase.map(subject).some(n => n)).toBeFalsy(); }); test('should return false when name is PascalCase', () => { - expect(targetNames.pascalCase.map(subject)).toEqual([false, false, false]); + expect(targetNames.pascalCase.map(subject).some(n => n)).toBeFalsy(); }); test('should return false when name is snake_case', () => { - expect(targetNames.snakeCase.map(subject)).toEqual([false, false, false]); + expect(targetNames.snakeCase.map(subject).some(n => n)).toBeFalsy(); + }); + + test('should return false when name is chaos case', () => { + expect(targetNames.chaosCase.map(subject).some(n => n)).toBeFalsy(); }); }); @@ -30,23 +34,27 @@ describe('Preset RegExp of kebab-case', () => { const subject = (name: string) => presetCases['kebab-case'].test(name); test('should return true when name is narmalcase', () => { - expect(targetNames.normalCase.map(subject)).toEqual([true, true, true]); + expect(targetNames.normalCase.map(subject).every(n => n)).toBeTruthy(); }); test('should return false when name is camelCase', () => { - expect(targetNames.camelCase.map(subject)).toEqual([false, false, false]); + expect(targetNames.camelCase.map(subject).some(n => n)).toBeFalsy(); }); test('should return true when name is kebab-case', () => { - expect(targetNames.kebabCase.map(subject)).toEqual([true, true, true]); + expect(targetNames.kebabCase.map(subject).every(n => n)).toBeTruthy(); }); test('should return false when name is PascalCase', () => { - expect(targetNames.pascalCase.map(subject)).toEqual([false, false, false]); + expect(targetNames.pascalCase.map(subject).some(n => n)).toBeFalsy(); }); test('should return false when name is snake_case', () => { - expect(targetNames.snakeCase.map(subject)).toEqual([false, false, false]); + expect(targetNames.snakeCase.map(subject).some(n => n)).toBeFalsy(); + }); + + test('should return false when name is chaos case', () => { + expect(targetNames.chaosCase.map(subject).some(n => n)).toBeFalsy(); }); }); @@ -54,23 +62,27 @@ describe('Preset RegExp of PascalCase', () => { const subject = (name: string) => presetCases.PascalCase.test(name); test('should return false when name is narmalcase', () => { - expect(targetNames.normalCase.map(subject)).toEqual([false, false, false]); + expect(targetNames.normalCase.map(subject).some(n => n)).toBeFalsy(); }); test('should return false when name is camelCase', () => { - expect(targetNames.camelCase.map(subject)).toEqual([false, false, false]); + expect(targetNames.camelCase.map(subject).some(n => n)).toBeFalsy(); }); test('should return false when name is kebab-case', () => { - expect(targetNames.kebabCase.map(subject)).toEqual([false, false, false]); + expect(targetNames.kebabCase.map(subject).some(n => n)).toBeFalsy(); }); test('should return true when name is PascalCase', () => { - expect(targetNames.pascalCase.map(subject)).toEqual([true, true, true]); + expect(targetNames.pascalCase.map(subject).every(n => n)).toBeTruthy(); }); test('should return false when name is snake_case', () => { - expect(targetNames.snakeCase.map(subject)).toEqual([false, false, false]); + expect(targetNames.snakeCase.map(subject).some(n => n)).toBeFalsy(); + }); + + test('should return false when name is chaos case', () => { + expect(targetNames.chaosCase.map(subject).some(n => n)).toBeFalsy(); }); }); @@ -78,22 +90,26 @@ describe('Preset RegExp of snake_case', () => { const subject = (name: string) => presetCases.snake_case.test(name); test('should return true when name is narmalcase', () => { - expect(targetNames.normalCase.map(subject)).toEqual([true, true, true]); + expect(targetNames.normalCase.map(subject).every(n => n)).toBeTruthy(); }); test('should return false when name is camelCase', () => { - expect(targetNames.camelCase.map(subject)).toEqual([false, false, false]); + expect(targetNames.camelCase.map(subject).some(n => n)).toBeFalsy(); }); test('should return false when name is kebab-case', () => { - expect(targetNames.kebabCase.map(subject)).toEqual([false, false, false]); + expect(targetNames.kebabCase.map(subject).some(n => n)).toBeFalsy(); }); test('should return false when name is PascalCase', () => { - expect(targetNames.pascalCase.map(subject)).toEqual([false, false, false]); + expect(targetNames.pascalCase.map(subject).some(n => n)).toBeFalsy(); }); test('should return true when name is snake_case', () => { - expect(targetNames.snakeCase.map(subject)).toEqual([true, true, true]); + expect(targetNames.snakeCase.map(subject).every(n => n)).toBeTruthy(); + }); + + test('should return false when name is chaos case', () => { + expect(targetNames.chaosCase.map(subject).some(n => n)).toBeFalsy(); }); }); diff --git a/tests/utils/seeds.json b/tests/utils/seeds.json index dc3d4f4b..57ac62e4 100644 --- a/tests/utils/seeds.json +++ b/tests/utils/seeds.json @@ -1,27 +1,38 @@ { "normalCase": [ "normalcase", + "threeormorewordsincludingnormalcase", "normal0case", "normalcase0" ], "camelCase": [ "camelCase", + "threeOrMoreWordsIncludingCamelCase", "camel0Case", "camelCase0" ], "kebabCase": [ "kebab-case", + "three-or-more-words-including-kebab-case", "kebab0-case", "kebab-case0" ], "pascalCase": [ "PascalCase", + "ThreeOrMoreWordsIncludingPascalCase", "Pascal0Case", "PascalCase0" ], "snakeCase": [ "snake_case", + "three_or_more_words_including_snake_case", "snake0_case", "snake_case0" + ], + "chaosCase": [ + "this_Is-A_SampleOfChaos-Case", + "ThisIs-1of_the sampleOFChaosCase", + "00001", + "" ] } diff --git a/yarn.lock b/yarn.lock index 958102cb..2eb94b43 100644 --- a/yarn.lock +++ b/yarn.lock @@ -729,6 +729,11 @@ anymatch@^3.0.3: normalize-path "^3.0.0" picomatch "^2.0.4" +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -1266,6 +1271,11 @@ diff-sequences@^26.0.0: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.0.0.tgz#0760059a5c287637b842bd7085311db7060e88a6" integrity sha512-JC/eHYEC3aSS0vZGjuoc4vHA0yAQTzhQQldXMeMF+JlxLGJlCO38Gma82NV9gk1jGFz8mDzUMeaKXvjRRdJ2dg== +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + doctrine@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" @@ -2794,7 +2804,7 @@ make-dir@^3.0.0: dependencies: semver "^6.0.0" -make-error@1.x: +make-error@1.x, make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== @@ -3687,7 +3697,7 @@ source-map-resolve@^0.5.0: source-map-url "^0.4.0" urix "^0.1.0" -source-map-support@^0.5.6: +source-map-support@^0.5.17, source-map-support@^0.5.6: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== @@ -4053,6 +4063,17 @@ ts-jest@^26.0.0: semver "7.x" yargs-parser "18.x" +ts-node@^8.10.2: + version "8.10.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d" + integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA== + dependencies: + arg "^4.1.0" + diff "^4.0.1" + make-error "^1.1.1" + source-map-support "^0.5.17" + yn "3.1.1" + tslib@^1.8.1, tslib@^1.9.0: version "1.13.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" @@ -4350,3 +4371,8 @@ yargs@^15.3.1: which-module "^2.0.0" y18n "^4.0.0" yargs-parser "^18.1.1" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==