power-linter 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -12
- package/package.json +4 -3
- package/scripts/addDataTestAttribute/addDataTestInDOM.js +135 -0
- package/src/index.js +10 -4
package/README.md
CHANGED
|
@@ -1,36 +1,66 @@
|
|
|
1
1
|
# power-linter
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Описание
|
|
4
|
+
power-linter — пакет пресетов конфигураций для ESLint и Prettier, кастомных правил линтирования и codemode-скриптов. Позволяет переносить одни и те же пресеты конфигураций для EsLint и Prettier, скрипты и правила , не дублируя их в репозиториях проектов.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
Содержит правила из плагинов:
|
|
7
|
+
"eslint-config-airbnb",
|
|
8
|
+
"eslint-config-prettier",
|
|
9
|
+
"eslint-plugin-import",
|
|
10
|
+
"eslint-plugin-jsx-a11y",
|
|
11
|
+
"eslint-plugin-power-esrules",
|
|
12
|
+
"eslint-plugin-react",
|
|
13
|
+
"eslint-plugin-react-hooks".
|
|
7
14
|
|
|
8
|
-
|
|
15
|
+
Содержит codemod-скрипты:
|
|
16
|
+
1. Для добавления data-аттрибута "data-testid" (add-data-testid.js).
|
|
17
|
+
2. Для преобразования классовых React-компонентов в функциональные (class-to-functional.js).
|
|
9
18
|
|
|
10
|
-
|
|
19
|
+
Статус: в разработке.
|
|
11
20
|
|
|
21
|
+
## Содержание
|
|
22
|
+
- [Описание](#описание)
|
|
23
|
+
- [Технологии](#технологии)
|
|
24
|
+
- [Интеграция](#интеграция)
|
|
25
|
+
- [Требования](#требования)
|
|
26
|
+
|
|
27
|
+
## Технологии
|
|
28
|
+
- [JavaScript](https://developer.mozilla.org/ru/docs/Web/JavaScript)
|
|
29
|
+
- ...
|
|
30
|
+
|
|
31
|
+
## Требования
|
|
32
|
+
Eslint требуется не ниже версии 8.0.0.
|
|
33
|
+
Prettier требуется не ниже версии 2.6.2.
|
|
34
|
+
|
|
35
|
+
## Интеграция
|
|
36
|
+
Для интеграции в проект предварительно необходимо:
|
|
37
|
+
|
|
38
|
+
0. Установить EsLint и Prettier.
|
|
12
39
|
```bash
|
|
13
|
-
npm
|
|
40
|
+
$ npm i eslint
|
|
41
|
+
$ npm i prettier
|
|
14
42
|
```
|
|
15
43
|
|
|
16
|
-
|
|
44
|
+
1. Установите npm-пакет с помощью команды:
|
|
45
|
+
```bash
|
|
46
|
+
$ npm install power-linter --save-dev
|
|
47
|
+
```
|
|
17
48
|
|
|
49
|
+
2. Добавьте экспорт конфигурации eslint
|
|
18
50
|
```js
|
|
19
51
|
// .eslintrc.js
|
|
20
52
|
module.exports = require('power-linter/src/eslint/config');
|
|
21
53
|
```
|
|
22
54
|
|
|
23
55
|
3. Добавьте экспорт конфигурации Prettier
|
|
24
|
-
|
|
25
56
|
```js
|
|
26
57
|
// .prettierrc.js
|
|
27
58
|
module.exports = require('power-linter/src/prettier/config');
|
|
28
59
|
```
|
|
29
60
|
|
|
30
|
-
4. Для
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
61
|
+
4. Для портирования настроек vscode (если необходимо) в каталоге .vscode создайте файл settings.json и добавьте в него следующие настройки:
|
|
62
|
+
```json
|
|
63
|
+
// settings.json
|
|
34
64
|
`{
|
|
35
65
|
"eslint.enable": true,
|
|
36
66
|
"eslint.validate": [
|
|
@@ -69,3 +99,5 @@ module.exports = require('power-linter/src/prettier/config');
|
|
|
69
99
|
}`
|
|
70
100
|
|
|
71
101
|
---
|
|
102
|
+
|
|
103
|
+
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "power-linter",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "power-linter —
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "power-linter — пакет пресетов конфигураций для ESLint и Prettier, кастомных правил линтирования и скриптов.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"author": "vollmond148",
|
|
7
7
|
"license": "MIT",
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"prettier": "^3.2.5"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
|
-
"eslint": "^8.0.0"
|
|
32
|
+
"eslint": "^8.0.0",
|
|
33
|
+
"prettier": "^2.6.2"
|
|
33
34
|
},
|
|
34
35
|
"files": [
|
|
35
36
|
"src/",
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
const STABLE_ID_ATTRIBUTE_NAME = "data-test";
|
|
2
|
+
const COMPONENT_TEST_NAME = "data-testid";
|
|
3
|
+
const KEYS_COUNTS_MAP_MAX_SIZE = 1000;
|
|
4
|
+
|
|
5
|
+
const repeatedKeysCountsMap = new Map();
|
|
6
|
+
|
|
7
|
+
export const clearRepeatedKeysCountsMap = () => {
|
|
8
|
+
repeatedKeysCountsMap.clear();
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
function encodeStableId(str) {
|
|
12
|
+
let hash = 0;
|
|
13
|
+
for (let i = 0; i < str.length; i += 4) {
|
|
14
|
+
// eslint-disable-next-line no-bitwise
|
|
15
|
+
hash = (hash * 31 + str.charCodeAt(i)) >>> 0; // 32-битный хеш
|
|
16
|
+
}
|
|
17
|
+
const base36 = hash.toString(36);
|
|
18
|
+
return base36.padStart(7, "0");
|
|
19
|
+
}
|
|
20
|
+
// собирает data-testid предков элемента до рутового компонента в строку
|
|
21
|
+
function collectHashesByParentsTestIds(el) {
|
|
22
|
+
const segments = [];
|
|
23
|
+
let node = el;
|
|
24
|
+
while (node && node.nodeType === Node.ELEMENT_NODE) {
|
|
25
|
+
if (node.hasAttribute(COMPONENT_TEST_NAME)) {
|
|
26
|
+
const testID = node.getAttribute(COMPONENT_TEST_NAME);
|
|
27
|
+
if (segments[segments.length - 1] !== testID) {
|
|
28
|
+
const testIDHash = testID.replace(/[_aeiouy-]/gi, "");
|
|
29
|
+
segments.push(testIDHash);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (
|
|
33
|
+
node.getAttribute &&
|
|
34
|
+
node.getAttribute(COMPONENT_TEST_NAME) === "app-root"
|
|
35
|
+
) {
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
node = node.parentElement;
|
|
39
|
+
}
|
|
40
|
+
return segments.join("");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Собираем текущие максимальные индексы для уже проставленных data-test
|
|
44
|
+
function buildCountsForExistedDataTests(rootNode) {
|
|
45
|
+
const existingDataTestNodes = rootNode.querySelectorAll(
|
|
46
|
+
`[${STABLE_ID_ATTRIBUTE_NAME}]`,
|
|
47
|
+
);
|
|
48
|
+
existingDataTestNodes.forEach((node) => {
|
|
49
|
+
const dataTestValue = node.getAttribute(STABLE_ID_ATTRIBUTE_NAME);
|
|
50
|
+
const dataTestParts = dataTestValue.split("::"); // [existingDataTestID, encodedID, index]
|
|
51
|
+
const groupedID = `${dataTestParts[0]}::${dataTestParts[1]}`;
|
|
52
|
+
const index = Number(dataTestParts[2]) || 0;
|
|
53
|
+
const prev = repeatedKeysCountsMap.get(groupedID) ?? 0;
|
|
54
|
+
if (index + 1 > prev) {
|
|
55
|
+
repeatedKeysCountsMap.set(groupedID, index + 1);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function applyDataTestIDsToDom(addedRootNode = document.body) {
|
|
61
|
+
if (!addedRootNode) return;
|
|
62
|
+
buildCountsForExistedDataTests(addedRootNode);
|
|
63
|
+
if (repeatedKeysCountsMap.size >= KEYS_COUNTS_MAP_MAX_SIZE) {
|
|
64
|
+
clearRepeatedKeysCountsMap();
|
|
65
|
+
}
|
|
66
|
+
const allAddedNodes = [];
|
|
67
|
+
if (!addedRootNode.hasAttribute(STABLE_ID_ATTRIBUTE_NAME)) {
|
|
68
|
+
allAddedNodes.push(addedRootNode);
|
|
69
|
+
}
|
|
70
|
+
const allNotDataTestChildren = addedRootNode.querySelectorAll(
|
|
71
|
+
`*:not([${STABLE_ID_ATTRIBUTE_NAME}])`,
|
|
72
|
+
);
|
|
73
|
+
allAddedNodes.push(...allNotDataTestChildren);
|
|
74
|
+
if (!allAddedNodes.length) return;
|
|
75
|
+
allAddedNodes.forEach((node) => {
|
|
76
|
+
let existingDataTestID = "";
|
|
77
|
+
if (node.hasAttribute(COMPONENT_TEST_NAME)) {
|
|
78
|
+
existingDataTestID = node.getAttribute(COMPONENT_TEST_NAME);
|
|
79
|
+
}
|
|
80
|
+
const fullHashChain = collectHashesByParentsTestIds(node);
|
|
81
|
+
const encodedID = encodeStableId(fullHashChain);
|
|
82
|
+
const groupedID = `${existingDataTestID}::${encodedID}`;
|
|
83
|
+
const nextIndex = repeatedKeysCountsMap.get(groupedID) ?? 0;
|
|
84
|
+
repeatedKeysCountsMap.set(groupedID, nextIndex + 1);
|
|
85
|
+
const resultID = `${groupedID}::${nextIndex}`;
|
|
86
|
+
node.setAttribute(STABLE_ID_ATTRIBUTE_NAME, resultID);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const clearNodeMapKeys = (removingRootNode = document.body) => {
|
|
91
|
+
const allRemovingNodes = [];
|
|
92
|
+
if (removingRootNode.hasAttribute(STABLE_ID_ATTRIBUTE_NAME)) {
|
|
93
|
+
allRemovingNodes.push(removingRootNode);
|
|
94
|
+
}
|
|
95
|
+
const allDataTestChildren = removingRootNode.querySelectorAll(
|
|
96
|
+
`[${STABLE_ID_ATTRIBUTE_NAME}]`,
|
|
97
|
+
);
|
|
98
|
+
allRemovingNodes.push(...allDataTestChildren);
|
|
99
|
+
allRemovingNodes.forEach((node) => {
|
|
100
|
+
const dataTestValueArray = node
|
|
101
|
+
.getAttribute(STABLE_ID_ATTRIBUTE_NAME)
|
|
102
|
+
.split("::");
|
|
103
|
+
dataTestValueArray.pop();
|
|
104
|
+
const nodeKey = dataTestValueArray.join("::");
|
|
105
|
+
const keyCount = repeatedKeysCountsMap.get(nodeKey);
|
|
106
|
+
if (keyCount) {
|
|
107
|
+
repeatedKeysCountsMap.set(nodeKey, keyCount - 1);
|
|
108
|
+
} else {
|
|
109
|
+
repeatedKeysCountsMap.delete(nodeKey);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export function initStableIds() {
|
|
115
|
+
if (typeof document === "undefined") return;
|
|
116
|
+
applyDataTestIDsToDom();
|
|
117
|
+
const observer = new MutationObserver((mutations) => {
|
|
118
|
+
mutations.forEach((mutationRecord) => {
|
|
119
|
+
mutationRecord.removedNodes.forEach((node) => {
|
|
120
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
121
|
+
clearNodeMapKeys(node);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
mutationRecord.addedNodes.forEach((node) => {
|
|
125
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
126
|
+
applyDataTestIDsToDom(node);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
observer.observe(document.body, {
|
|
132
|
+
childList: true,
|
|
133
|
+
subtree: true,
|
|
134
|
+
});
|
|
135
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
const eslintConfig = require(
|
|
2
|
-
const prettierConfig = require(
|
|
1
|
+
const eslintConfig = require("./eslint/config");
|
|
2
|
+
const prettierConfig = require("./prettier/config");
|
|
3
|
+
const {
|
|
4
|
+
initStableIds,
|
|
5
|
+
clearRepeatedKeysCountsMap,
|
|
6
|
+
} = require("../scripts/addDataTestAttribute/addDataTestInDOM.js");
|
|
3
7
|
|
|
4
8
|
module.exports = {
|
|
5
|
-
|
|
6
|
-
|
|
9
|
+
eslintConfig,
|
|
10
|
+
prettierConfig,
|
|
11
|
+
initStableIds,
|
|
12
|
+
clearRepeatedKeysCountsMap,
|
|
7
13
|
};
|