Skip to content

Commit

Permalink
add naming convention checks to eslint config
Browse files Browse the repository at this point in the history
  • Loading branch information
Miguel7373 committed Dec 19, 2024
1 parent 3e22f98 commit 1b54bfd
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 2 deletions.
3 changes: 3 additions & 0 deletions frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ https://eslint.org/
We use **prettier** to format scss json and yaml files:
https://prettier.io/

If you get a does not match pattern error, it is likely because of the custom words for the OKR tool
here is a regex101 project that showcases how the regex works:
https://regex101.com/r/VUyAt6/1
## Test Coverage

For generating the coverage of our frontend specs (jest), you can use following command:
Expand Down
33 changes: 32 additions & 1 deletion frontend/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ import html from '@html-eslint/eslint-plugin'
import angular from 'angular-eslint'
import htmlParser from '@html-eslint/parser'

import tsEslint from "typescript-eslint";
import html from "@html-eslint/eslint-plugin";
import { createRegexForWords } from "./eslintHelper.mjs"
import * as constants from "node:constants";

export default tsEslint.config(
{
files: ['**/*.ts'],
Expand All @@ -15,6 +20,11 @@ export default tsEslint.config(
...tsEslint.configs.stylistic,
...angular.configs.tsRecommended,
],
languageOptions: {
parserOptions: {
project: [ "./tsconfig.json","./tsconfig.spec.json"],
}
},
processor: angular.processInlineTemplates,
rules: {
...stylistic.configs['all-flat'].rules,
Expand Down Expand Up @@ -73,8 +83,28 @@ export default tsEslint.config(
style: 'kebab-case',
},
],
},
"@typescript-eslint/naming-convention": ["error", {

selector: ["class", "interface"], format: ["PascalCase"]
}, {
selector: "variable", modifiers: [], format: ["camelCase", "UPPER_CASE"]
}, {
selector: "enum", format: ["PascalCase"]
}, {
selector: "enumMember", format: ["UPPER_CASE"]
}, {
selector: ["method", "function"], format: ["camelCase"]
}, {
selector: "typeParameter", format: ["PascalCase"]
}
],
"id-match": [
"error",
createRegexForWords(["KeyResult", "CheckIn", "TeamManagement", "StretchGoal"])
]
}
},

{
files: ['**/*.spec.ts'],
rules: {
Expand All @@ -98,6 +128,7 @@ export default tsEslint.config(
'@html-eslint/require-img-alt': 'off',
'@html-eslint/element-newline': 'off',
'@html-eslint/require-closing-tags': ['error', { selfClosing: 'always' }],
"@html-eslint/id-naming-convention": ["error", "regex", {pattern: `(?=(^[a-z]+(-[a-z]+)*$))(?=(${createRegexForWords(["KeyResult", "CheckIn", "TeamManagement", "StretchGoal"])}))`}],
},
},
{
Expand Down
63 changes: 63 additions & 0 deletions frontend/eslintHelper.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@

export function createRegexForWords(wordList) {

// This function builds a case-insensitive regex pattern for a given word.


const part1List = [];
const part2List = [];

wordList.forEach(word => {
const part1 = createRegexToCheckIfWordLookAlike(word);
const part2 = createFallbackRegex(word);
part1List.push(part1);
part2List.push(part2);
});
return `(${part1List.join('|')})|(${part2List.join('')})`;
}

function getCaseInsensitiveRegexForChar(c){
return `[${c.toUpperCase()}${c.toLowerCase()}]`
}

function createCaseInsensitiveRegexForWord(word) {
return word
.match(/[A-Z][a-z]+/g)
.join(".?")
.split("")
.map(c => /[a-zA-Z]/g.test(c) ?getCaseInsensitiveRegexForChar(c) : c)
.join('');
}

function transformToUnderscoreUppercase(word) {
return word
.split(/(?=[A-Z])/) // Split at uppercase letters without removing them
.join('_')
.toUpperCase();
}

function transformToHyphenLowercase(word) {
return word
.split(/(?=[A-Z])/) // Split at uppercase letters without removing them
.join('-')
.toLowerCase();
}

function getWordRegexWithOptionalLetters(word) {
return word.replace(/(\[[^\[\]]+\])(?![.?])/g, "$1?"); // Puts a "?" between the case-insensitive braces if there is no "?" or "." already
}

function createRegexToCheckIfWordLookAlike(word) {
let wordLooksLikeRegex = createCaseInsensitiveRegexForWord(word);
wordLooksLikeRegex = getWordRegexWithOptionalLetters(wordLooksLikeRegex)
const wordCorrectRegex = getCaseInsensitiveRegexForChar(word[0]) + word.slice(1);
const wordInUpperCase = transformToUnderscoreUppercase(word)
const wordInLowerCase = transformToHyphenLowercase(word)
return `(?=.*${wordLooksLikeRegex}.*)(.*${wordCorrectRegex}.*|[A-Z_]*${wordInUpperCase}[A-Z_]*|[a-z-]*${wordInLowerCase}[a-z-]*)`;
}

function createFallbackRegex(word) {
const caseInsensitiveWordRegex = createCaseInsensitiveRegexForWord(word);
return `(?=^(?!.*${caseInsensitiveWordRegex}).*)`;
}

31 changes: 31 additions & 0 deletions frontend/eslintHelper.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as eslintHelper from "./eslintHelper.mjs"
import { $locationShim } from "@angular/common/upgrade";

describe("eslintHelper", () => {
const combinedRegex:string = "((?=.*[Kk]?[Ee]?[Yy].?[Rr]?[Ee]?[Ss]?[Uu]?[Ll]?[Tt]?.*)(.*[Kk]eyResult.*|[A-Z_]*KEY_RESULT[A-Z_]*|[a-z-]*key-result[a-z-]*)|(?=.*[Cc]?[Hh]?[Ee]?[Cc]?[Kk].?[Ii]?[Nn]?.*)(.*[Cc]heckIn.*|[A-Z_]*CHECK_IN[A-Z_]*|[a-z-]*check-in[a-z-]*))|((?=^(?!.*[Kk][Ee][Yy].?[Rr][Ee][Ss][Uu][Ll][Tt]).*)(?=^(?!.*[Cc][Hh][Ee][Cc][Kk].?[Ii][Nn]).*))"
const keyResultRegex:string = "((?=.*[Kk]?[Ee]?[Yy].?[Rr]?[Ee]?[Ss]?[Uu]?[Ll]?[Tt]?.*)(.*[Kk]eyResult.*|[A-Z_]*KEY_RESULT[A-Z_]*|[a-z-]*key-result[a-z-]*))|((?=^(?!.*[Kk][Ee][Yy].?[Rr][Ee][Ss][Uu][Ll][Tt]).*))"
const checkInRegex:string = "((?=.*[Cc]?[Hh]?[Ee]?[Cc]?[Kk].?[Ii]?[Nn]?.*)(.*[Cc]heckIn.*|[A-Z_]*CHECK_IN[A-Z_]*|[a-z-]*check-in[a-z-]*))|((?=^(?!.*[Cc][Hh][Ee][Cc][Kk].?[Ii][Nn]).*))"

it.each([
[["KeyResult"], keyResultRegex],
[["CheckIn"], checkInRegex],
[["KeyResult", "CheckIn"], combinedRegex],
])("should return regex %p", (wordToRegex, expectedRegex) => {
expect(eslintHelper.createRegexForWords(wordToRegex)).toEqual(expectedRegex);
});

it.each([
[["KeyResult"], ["KeyResult", "CurrentKeyResult", "keyResult", "keyResultId", "key-result", "test-key-result-test"],["Keyresult", "CurrentKeyresult", "keyresult", "keyresultId", "KEyResult", "KeyResUlt", "test-keyresult-test"]],
[["CheckIn"], ["CheckIn", "CurrentCheckIn", "checkIn", "checkInId", "check-in", "test-check-in-test"],["Checkin", "CurrentCheckin", "checkin", "checkinId", "cHeckIn", "checkIN", "test-checkin-test"]],
[["KeyResult", "CheckIn"], ["KeyResult", "CurrentKeyResult", "keyResult", "keyResultId", "key-result", "test-key-result-test", "CheckIn", "CurrentCheckIn", "checkIn", "checkInId", "check-in", "test-check-in-test"],["Keyresult", "CurrentKeyresult", "keyresult", "keyresultId", "KEyResult", "KeyResUlt", "test-keyresult-test", "Checkin", "CurrentCheckin", "checkin", "checkinId", "cHeckIn", "checkIN", "test-checkin-test"]],
])("should run regex %p threw the matching and not matching list", (wordToRegex:string[], matchingListToRegex:string[], notMatchingListToRegex:string[]) => {
const regexOfCustomWord = new RegExp(eslintHelper.createRegexForWords(wordToRegex));
matchingListToRegex = matchingListToRegex.filter(word => regexOfCustomWord.test(word));
notMatchingListToRegex = notMatchingListToRegex.filter(word => regexOfCustomWord.test(word));

expect(matchingListToRegex.length).toBe(matchingListToRegex.length);
expect(notMatchingListToRegex.length).toBe(0)
});

})

3 changes: 2 additions & 1 deletion frontend/tsconfig.spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
"module": "CommonJs",
"types": ["jest"]
},
"include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
"include": ["src/**/*.spec.ts", "src/**/*.d.ts"],
"exclude": []
}

0 comments on commit 1b54bfd

Please sign in to comment.