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 CHANGED
@@ -1,36 +1,66 @@
1
1
  # power-linter
2
2
 
3
- power-linter — линтер (ESLint) и форматтер (Prettier) с расширяемой архитектурой для произвольных правил и скриптов.
3
+ ## Описание
4
+ power-linter — пакет пресетов конфигураций для ESLint и Prettier, кастомных правил линтирования и codemode-скриптов. Позволяет переносить одни и те же пресеты конфигураций для EsLint и Prettier, скрипты и правила , не дублируя их в репозиториях проектов.
4
5
 
5
- - конфиги eslint и prettier из коробки
6
- - кастомные правила-линтера (see `eslint-plugin-power-esrules`)
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
- 1. Установка:
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 install power-linter --save-dev
40
+ $ npm i eslint
41
+ $ npm i prettier
14
42
  ```
15
43
 
16
- 2. Добавьте экспорт конфигурации eslint
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. Для кастомных правил добавляйте файлы в `eslint-plugin-power-esrules`
31
-
32
- 5. Добавьте в корневой каталог проекта настройки vscode в каталоге .vscode создайте файл settings.json и добавьте в него
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.0",
4
- "description": "power-linter — линтер (ESLint) и форматтер (Prettier) с расширяемой архитектурой для произвольных правил и скриптов.",
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('./eslint/config');
2
- const prettierConfig = require('./prettier/config');
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
- eslintConfig,
6
- prettierConfig,
9
+ eslintConfig,
10
+ prettierConfig,
11
+ initStableIds,
12
+ clearRepeatedKeysCountsMap,
7
13
  };