@mimik/eslint-plugin-document-env 2.0.7 → 2.0.9

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,31 +1,144 @@
1
- # eslint-plugin-document-env
1
+ # @mimik/eslint-plugin-document-env
2
+
3
+ An ESLint plugin that validates that all `process.env` usages are properly documented in markdown-style block comments.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install --save-dev @mimik/eslint-plugin-document-env
9
+ ```
2
10
 
3
11
  ## Usage
4
12
 
5
- ```sh
6
- npm install @mimik/eslint-plugin-document-env
13
+ Add the plugin to your `eslint.config.js`:
14
+
15
+ ```js
16
+ import documentEnv from '@mimik/eslint-plugin-document-env';
17
+
18
+ export default [
19
+ {
20
+ plugins: {
21
+ '@mimik/document-env': documentEnv,
22
+ },
23
+ rules: {
24
+ '@mimik/document-env/validate-document-env': 'error',
25
+ },
26
+ },
27
+ ];
7
28
  ```
8
29
 
9
- In your `.eslintrc`:
10
-
11
- ```json
12
- {
13
- "plugins": [
14
- "@mimik/document-env"
15
- ],
16
- "rules": {
17
- "@mimik/document-env/validate-document-env": 2
18
- }
19
- }
30
+ ## Rule: `validate-document-env`
31
+
32
+ Verifies that when an environment variable is used via `process.env`, there is a corresponding documentation entry in a block comment with the following table header:
33
+
34
+ ```
35
+ * | Env variable name | Description | Default | Comments |
36
+ * | ----------------- | ----------- | ------- | -------- |
20
37
  ```
21
- ## Rules
22
38
 
23
- An [eslint](https://github.com/eslint/eslint) plugin that ...
39
+ ### What Gets Checked
40
+
41
+ - Every `process.env.VAR_NAME` or `process.env['VAR_NAME']` access must have a matching row in a documentation table
42
+ - The description column must not be empty or blank
43
+ - The variable must not be documented more than once (across separate tables or within the same table)
44
+
45
+ ### Messages
46
+
47
+ | Message ID | Description |
48
+ |---|---|
49
+ | `unDocumentedProcessEnv` | No documentation table entry found for the variable |
50
+ | `unDescribedProcessEnv` | Table entry exists but the description column is empty |
51
+ | `duplicatedProcessEnv` | Variable is documented in multiple tables or multiple rows |
24
52
 
25
- ### `document-env/validate-document-env`
53
+ ### Example
26
54
 
27
- Verifies that when an environment variable is used, there is a document ation about it after the following header:
55
+ ```js
56
+ /**
28
57
  * | Env variable name | Description | Default | Comments |
29
58
  * | ----------------- | ----------- | ------- | -------- |
59
+ * | PORT | The port the server listens on | 3000 | |
60
+ * | NODE_ENV | Runtime environment | development | |
61
+ */
62
+ const port = process.env.PORT; // OK
63
+ const env = process.env.NODE_ENV; // OK
64
+ const secret = process.env.SECRET; // Error: Undocumented environment variable: SECRET
65
+ ```
66
+
67
+ ## API Reference
68
+
69
+ ## Functions
70
+
71
+ <dl>
72
+ <dt><a href="#isProcessEnvAccess">isProcessEnvAccess(node)</a> ⇒ <code>boolean</code></dt>
73
+ <dd><p>Checks whether an AST node represents a <code>process.env</code> member expression.</p>
74
+ </dd>
75
+ <dt><a href="#regular">regular(propName)</a> ⇒ <code>RegExp</code></dt>
76
+ <dd><p>Builds a regex that matches a documentation table row for the given env variable name.</p>
77
+ </dd>
78
+ <dt><a href="#extractEnvVarName">extractEnvVarName(node, context)</a> ⇒ <code>string</code> | <code>undefined</code></dt>
79
+ <dd><p>Extracts the environment variable name from a <code>process.env.X</code> or <code>process.env[&#39;X&#39;]</code> access.
80
+ For computed access with a non-literal key (e.g. <code>process.env[varName]</code>), falls back to
81
+ extracting the source text of the computed expression.</p>
82
+ </dd>
83
+ <dt><a href="#findEnvDocInComments">findEnvDocInComments(comments, regex)</a> ⇒ <code>Array.&lt;Object&gt;</code></dt>
84
+ <dd><p>Finds block comments that contain a documentation table entry for the given env variable.</p>
85
+ </dd>
86
+ </dl>
87
+
88
+ <a name="isProcessEnvAccess"></a>
89
+
90
+ ## isProcessEnvAccess(node) ⇒ <code>boolean</code>
91
+ Checks whether an AST node represents a `process.env` member expression.
92
+
93
+ **Kind**: global function
94
+ **Returns**: <code>boolean</code> - `true` if the node is a non-computed `process.env` access.
95
+
96
+ | Param | Type | Description |
97
+ | --- | --- | --- |
98
+ | node | <code>Object</code> | The AST MemberExpression node to check. |
99
+
100
+ <a name="regular"></a>
101
+
102
+ ## regular(propName) ⇒ <code>RegExp</code>
103
+ Builds a regex that matches a documentation table row for the given env variable name.
104
+
105
+ **Kind**: global function
106
+ **Returns**: <code>RegExp</code> - A multiline, global, unicode regex with two capture groups:
107
+ (1) the variable name and (2) the description cell content.
108
+
109
+ | Param | Type | Description |
110
+ | --- | --- | --- |
111
+ | propName | <code>string</code> | The environment variable name to search for. |
112
+
113
+ <a name="extractEnvVarName"></a>
114
+
115
+ ## extractEnvVarName(node, context) ⇒ <code>string</code> \| <code>undefined</code>
116
+ Extracts the environment variable name from a `process.env.X` or `process.env['X']` access.
117
+ For computed access with a non-literal key (e.g. `process.env[varName]`), falls back to
118
+ extracting the source text of the computed expression.
119
+
120
+ **Kind**: global function
121
+ **Returns**: <code>string</code> \| <code>undefined</code> - The env variable name, or `undefined` if the property node is missing.
122
+
123
+ | Param | Type | Description |
124
+ | --- | --- | --- |
125
+ | node | <code>Object</code> | The `process.env` MemberExpression node. |
126
+ | context | <code>Object</code> | The ESLint rule context. |
127
+
128
+ <a name="findEnvDocInComments"></a>
129
+
130
+ ## findEnvDocInComments(comments, regex) ⇒ <code>Array.&lt;Object&gt;</code>
131
+ Finds block comments that contain a documentation table entry for the given env variable.
132
+
133
+ **Kind**: global function
134
+ **Returns**: <code>Array.&lt;Object&gt;</code> - Matching block comments.
135
+
136
+ | Param | Type | Description |
137
+ | --- | --- | --- |
138
+ | comments | <code>Array.&lt;Object&gt;</code> | All comments in the source file. |
139
+ | regex | <code>RegExp</code> | The regex to match the env variable documentation row. |
140
+
141
+
142
+ ## License
30
143
 
31
- The plugin also verifies that there is a non empty or blank description and that the description is not duplicated.
144
+ MIT
package/index.js CHANGED
@@ -1,3 +1,7 @@
1
+ import { createRequire } from 'node:module';
2
+
3
+ const { version, name } = createRequire(import.meta.url)('./package.json');
4
+
1
5
  const HEADER = ' * | Env variable name | Description | Default | Comments |\n';
2
6
  const LINE = ' * | ----------------- | ----------- | ------- | -------- |\n';
3
7
  const SINGLE = 1;
@@ -5,42 +9,80 @@ const NO_DESCRIPTION = 2;
5
9
  const FIRST = 0;
6
10
  const EMPTY = 0;
7
11
 
12
+ /**
13
+ * Checks whether an AST node represents a `process.env` member expression.
14
+ * @param {Object} node - The AST MemberExpression node to check.
15
+ * @returns {boolean} `true` if the node is a non-computed `process.env` access.
16
+ */
8
17
  const isProcessEnvAccess = node => (
9
18
  node.object?.name === 'process'
10
19
  && !node.computed
11
20
  && node.property?.name === 'env'
12
21
  );
22
+
23
+ /**
24
+ * Builds a regex that matches a documentation table row for the given env variable name.
25
+ * @param {string} propName - The environment variable name to search for.
26
+ * @returns {RegExp} A multiline, global, unicode regex with two capture groups:
27
+ * (1) the variable name and (2) the description cell content.
28
+ */
13
29
  const regular = propName => new RegExp(
14
- `^ \\* \\| (${propName.replace(/[|\\{}()[\]^$+*?.]/gu, '\\$&')}) \\| *([^\\|]*)`,
30
+ `^ \\* \\| (${propName.replace(/[|\\{}()[\]^$+*?.]/gu, '\\$&')}) \\| *([^|]*)`,
15
31
  'mgu',
16
32
  );
17
- const extractEnvVarName = (node, context) => node.parent?.property?.name
18
- || context.getSourceCode().text.slice(node.parent.property.start, node.parent.property.end);
19
- const findEnvDocInComments = (comments, propName) => {
20
- const regex = regular(propName);
21
33
 
22
- return comments.filter(comment =>
23
- comment.type === 'Block'
24
- && comment.value.includes(HEADER)
25
- && comment.value.includes(LINE)
26
- && comment.value.match(regex),
27
- );
34
+ /**
35
+ * Extracts the environment variable name from a `process.env.X` or `process.env['X']` access.
36
+ * For computed access with a non-literal key (e.g. `process.env[varName]`), falls back to
37
+ * extracting the source text of the computed expression.
38
+ * @param {Object} node - The `process.env` MemberExpression node.
39
+ * @param {Object} context - The ESLint rule context.
40
+ * @returns {string|undefined} The env variable name, or `undefined` if the property node is missing.
41
+ */
42
+ const extractEnvVarName = (node, context) => {
43
+ const prop = node.parent?.property;
44
+
45
+ if (!prop) return undefined;
46
+ if (prop.name) return prop.name;
47
+ if (prop.value) return String(prop.value);
48
+
49
+ return context.sourceCode.text.slice(prop.range[FIRST], prop.range[SINGLE]);
28
50
  };
29
51
 
52
+ /**
53
+ * Finds block comments that contain a documentation table entry for the given env variable.
54
+ * @param {Array.<Object>} comments - All comments in the source file.
55
+ * @param {RegExp} regex - The regex to match the env variable documentation row.
56
+ * @returns {Array.<Object>} Matching block comments.
57
+ */
58
+ const findEnvDocInComments = (comments, regex) => comments.filter(comment =>
59
+ comment.type === 'Block'
60
+ && comment.value.includes(HEADER)
61
+ && comment.value.includes(LINE)
62
+ && comment.value.match(regex),
63
+ );
64
+
30
65
  const plugin = {
66
+ meta: {
67
+ name,
68
+ version,
69
+ },
31
70
  rules: {
32
71
  'validate-document-env': {
33
72
  create(context) {
34
- const { comments } = context.getSourceCode().ast;
73
+ const { comments } = context.sourceCode.ast;
35
74
 
36
75
  return {
37
76
  MemberExpression(node) {
38
77
  if (!isProcessEnvAccess(node) || !node.parent?.property) return;
39
78
 
40
79
  const propName = extractEnvVarName(node, context);
41
- const matchedComment = findEnvDocInComments(comments, propName);
42
80
 
43
- if (!matchedComment || matchedComment.length === EMPTY) {
81
+ if (!propName) return;
82
+ const regex = regular(propName);
83
+ const matchedComment = findEnvDocInComments(comments, regex);
84
+
85
+ if (matchedComment.length === EMPTY) {
44
86
  context.report({
45
87
  node,
46
88
  messageId: 'unDocumentedProcessEnv',
@@ -56,7 +98,6 @@ const plugin = {
56
98
  });
57
99
  return;
58
100
  }
59
- const regex = regular(propName);
60
101
  let match = matchedComment[FIRST].value.match(regex);
61
102
 
62
103
  if (match?.length > SINGLE) {
@@ -68,7 +109,7 @@ const plugin = {
68
109
  }
69
110
  else {
70
111
  match = regex.exec(matchedComment[FIRST].value);
71
- if (!match[NO_DESCRIPTION]) {
112
+ if (!match || !match[NO_DESCRIPTION]) {
72
113
  context.report({
73
114
  node,
74
115
  messageId: 'unDescribedProcessEnv',
package/package.json CHANGED
@@ -1,19 +1,20 @@
1
1
  {
2
2
  "name": "@mimik/eslint-plugin-document-env",
3
- "version": "2.0.7",
4
- "description": "validation of environement variable documentation",
3
+ "version": "2.0.9",
4
+ "description": "validation of environment variable documentation",
5
5
  "main": "./index.js",
6
6
  "type": "module",
7
+ "exports": "./index.js",
8
+ "engines": {
9
+ "node": ">=24.0.0"
10
+ },
7
11
  "scripts": {
12
+ "docs": "jsdoc2md --template docs/README.hbs index.js > README.md",
8
13
  "lint": "eslint . --no-error-on-unmatched-pattern",
9
- "test": "node --test test/*.test.js",
10
- "prepublishOnly": "npm run lint && npm run test"
11
- },
12
- "husky": {
13
- "hooks": {
14
- "pre-commit": "npm run commit-ready",
15
- "pre-push": "npm run test"
16
- }
14
+ "test": "mocha --reporter mochawesome --bail --exit --check-leaks test/",
15
+ "test-ci": "c8 --reporter=lcov --reporter=text npm test",
16
+ "prepublishOnly": "npm run docs && npm run lint && npm run test-ci",
17
+ "commit-ready": "npm run docs && npm run lint && npm run test-ci"
17
18
  },
18
19
  "keywords": [
19
20
  "mimik",
@@ -29,10 +30,15 @@
29
30
  "url": "git+https://bitbucket.org/mimiktech/eslint-plugin-document-env.git"
30
31
  },
31
32
  "devDependencies": {
32
- "@eslint/js": "9.30.1",
33
- "@stylistic/eslint-plugin": "5.1.0",
34
- "eslint": "9.30.1",
33
+ "@eslint/js": "9.39.4",
34
+ "@stylistic/eslint-plugin": "5.10.0",
35
+ "c8": "11.0.0",
36
+ "eslint": "9.39.4",
35
37
  "eslint-plugin-import": "2.32.0",
36
- "husky": "9.1.7"
38
+ "globals": "17.4.0",
39
+ "husky": "9.1.7",
40
+ "jsdoc-to-markdown": "9.1.3",
41
+ "mocha": "11.7.5",
42
+ "mochawesome": "7.1.4"
37
43
  }
38
44
  }
package/eslint.config.js DELETED
@@ -1,58 +0,0 @@
1
- import importPlugin from 'eslint-plugin-import';
2
- import js from '@eslint/js';
3
- import stylistic from '@stylistic/eslint-plugin';
4
-
5
- const MAX_LENGTH_LINE = 180;
6
- const MAX_FUNCTION_PARAMETERS = 6;
7
- const MAX_LINES_IN_FILES = 600;
8
- const MAX_LINES_IN_FUNCTION = 150;
9
- const MAX_STATEMENTS_IN_FUNCTION = 45;
10
- const MIN_KEYS_IN_OBJECT = 10;
11
- const MAX_COMPLEXITY = 30;
12
-
13
- export default [
14
- {
15
- ignores: ['mochawesome-report/**', 'node_modules/**', 'dist/**'],
16
- },
17
- importPlugin.flatConfigs.recommended,
18
- stylistic.configs['recommended-flat'],
19
- js.configs.all,
20
- {
21
- languageOptions: {
22
- ecmaVersion: 2022,
23
- globals: {
24
- console: 'readonly',
25
- describe: 'readonly',
26
- it: 'readonly',
27
- require: 'readonly',
28
- },
29
- sourceType: 'module',
30
- },
31
- rules: {
32
- '@stylistic/brace-style': ['warn', 'stroustrup', { allowSingleLine: true }],
33
- '@stylistic/line-comment-position': ['off'],
34
- '@stylistic/semi': ['error', 'always'],
35
- 'capitalized-comments': ['off'],
36
- 'complexity': ['error', MAX_COMPLEXITY],
37
- 'curly': ['off'],
38
- 'id-length': ['error', { exceptions: ['x', 'y', 'z', 'i', 'j', 'k'] }],
39
- 'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
40
- 'import/no-unresolved': ['error', { amd: true, caseSensitiveStrict: true, commonjs: true }],
41
- 'init-declarations': ['off'],
42
- 'linebreak-style': ['off'],
43
- 'max-len': ['warn', MAX_LENGTH_LINE, { ignoreComments: true }],
44
- 'max-lines': ['warn', { max: MAX_LINES_IN_FILES, skipComments: true }],
45
- 'max-lines-per-function': ['warn', { max: MAX_LINES_IN_FUNCTION, skipComments: true }],
46
- 'max-params': ['error', MAX_FUNCTION_PARAMETERS],
47
- 'max-statements': ['warn', MAX_STATEMENTS_IN_FUNCTION],
48
- 'no-confusing-arrow': ['off'], // arrow isnt confusing
49
- 'no-inline-comments': ['off'],
50
- 'no-process-env': ['error'],
51
- 'no-ternary': ['off'],
52
- 'no-undefined': ['off'],
53
- 'one-var': ['error', 'never'],
54
- 'quotes': ['warn', 'single'],
55
- 'sort-keys': ['error', 'asc', { caseSensitive: true, minKeys: MIN_KEYS_IN_OBJECT, natural: false }],
56
- },
57
- },
58
- ];
@@ -1,69 +0,0 @@
1
- import { RuleTester } from 'eslint';
2
- import rule from '../index.js';
3
-
4
- const tester = new RuleTester({
5
- languageOptions: { ecmaVersion: 2022, sourceType: 'module' },
6
- });
7
-
8
- tester.run('validate-document-env', rule.rules['validate-document-env'], {
9
- valid: [
10
- {
11
- code: `
12
- /**
13
- * | Env variable name | Description | Default | Comments |
14
- * | ----------------- | ----------- | ------- | -------- |
15
- * | MY_VAR | when existent on non null, will translate an IPV6 address into IPV4 address when possible | |
16
- */
17
- const v = process.env.MY_VAR;
18
- `,
19
- },
20
- ],
21
- invalid: [
22
- {
23
- code: `
24
- const v = process.env.UNDOCUMENTED_VAR;
25
- `,
26
- errors: [{ messageId: 'unDocumentedProcessEnv' }],
27
- },
28
- {
29
- code: `
30
- /**
31
- * | Env variable name | Description | Default | Comments |
32
- * | ----------------- | ----------- | ------- | -------- |
33
- * | UNDESCRIBED_VAR | | none | - |
34
- */
35
- const v = process.env.UNDESCRIBED_VAR;
36
- `,
37
- errors: [{ messageId: 'unDescribedProcessEnv' }],
38
- },
39
- {
40
- code: `
41
- /**
42
- * | Env variable name | Description | Default | Comments |
43
- * | ----------------- | ----------- | ------- | -------- |
44
- * | DUP_VAR | a | none | - |
45
- */
46
- const separation = 'test';
47
- /**
48
- * | Env variable name | Description | Default | Comments |
49
- * | ----------------- | ----------- | ------- | -------- |
50
- * | DUP_VAR | b | none | - |
51
- */
52
- const v = process.env.DUP_VAR;
53
- `,
54
- errors: [{ messageId: 'duplicatedProcessEnv' }],
55
- },
56
- {
57
- code: `
58
- /**
59
- * | Env variable name | Description | Default | Comments |
60
- * | ----------------- | ----------- | ------- | -------- |
61
- * | DUP_VAR | a | none | - |
62
- * | DUP_VAR | b | none | - |
63
- */
64
- const v = process.env.DUP_VAR;
65
- `,
66
- errors: [{ messageId: 'duplicatedProcessEnv' }],
67
- },
68
- ],
69
- });