comment-variables 0.4.2 → 0.6.0

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.
@@ -31,11 +31,14 @@ const data = {
31
31
  "The flow that compresses actual comments into $COMMENT#* placeholders.", // $COMMENT#JSDOC#DEFINITIONS#COMPRESSCOMMENTSFLOW
32
32
  findAllImports:
33
33
  "Finds all import paths recursively related to a given file path.", // $COMMENT#JSDOC#DEFINITIONS#FINDALLIMPORTS
34
- processImport: "Processes recursively and resolves a single import path.", // $COMMENT#JSDOC#DEFINITIONS#PROCESSIMPORT
34
+ processImport:
35
+ "Processes recursively and resolves a single import path. (Unlike `findAllImports`, here `currentDir`, `cwd`, `visitedSet`, `depth`, and `maxDepth` aren't options because they are mandatory and not pre-parameterized.)", // $COMMENT#JSDOC#DEFINITIONS#PROCESSIMPORT
35
36
  flattenConfigData:
36
37
  "Flattens the config's data property into a one-dimensional object of $COMMENT-*-like keys and string values.", // $COMMENT#JSDOC#DEFINITIONS#FLATTENCONFIGDATA
37
38
  resolveConfig:
38
39
  "Verifies, validates and resolves the config path to retrieve the config's data and ignores.", // $COMMENT#JSDOC#DEFINITIONS#RESOLVECONFIG
40
+ logError:
41
+ 'Logs an error to the console depending on its type. (`"error"` or `"warning"`.)', // $COMMENT#JSDOC#DEFINITIONS#LOGERROR
39
42
  }),
40
43
  params: Object.freeze({
41
44
  string: "The string.", // $COMMENT#JSDOC#PARAMS#STRING
@@ -51,24 +54,33 @@ const data = {
51
54
  "Either the flattened config data or the reversed flattened config data, since they share the same structure.", // $COMMENT#JSDOC#PARAMS#EITHERFLATTENEDCONFIGDATA
52
55
  filePath:
53
56
  "The absolute path of the file whose imports are being recursively found, such as that of a project's `comments.config.js` file.", // $COMMENT#JSDOC#PARAMS#FILEPATH
54
- cwd: "The current working directory, set as `process.cwd()` by default.", // $COMMENT#JSDOC#PARAMS#CWD
55
- visitedSet:
56
- "The set of strings tracking the import paths that have already been visited, instantiated as a `new Set()` by default.", // $COMMENT#JSDOC#PARAMS#VISITEDSET
57
- depth:
58
- "The current depth of the recursion, instantiated at `0` by default.", // $COMMENT#JSDOC#PARAMS#DEPTH
59
- maxDepth:
60
- "The maximum depth allowed for the recursion, instantiated at `100` by default.", // $COMMENT#JSDOC#PARAMS#MAXDEPTH
57
+ cwdOption:
58
+ "The current working directory, set as `process.cwd()` by default.", // $COMMENT#JSDOC#PARAMS#CWDOPTION
59
+ visitedSetOption:
60
+ "The set of strings tracking the import paths that have already been visited, instantiated as a `new Set()` by default.", // $COMMENT#JSDOC#PARAMS#VISITEDSETOPTION
61
+ depthOption:
62
+ "The current depth of the recursion, instantiated at `0` by default.", // $COMMENT#JSDOC#PARAMS#DEPTHOPTION
63
+ maxDepthOption:
64
+ "The maximum depth allowed for the recursion, instantiated at `100` by default.", // $COMMENT#JSDOC#PARAMS#MAXDEPTHOPTION
61
65
  importPath: "The import path currently being addressed.", // $COMMENT#JSDOC#PARAMS#IMPORTPATH
62
- currentDir:
63
- "The directory containing the import path currently being addressed.", // $COMMENT#JSDOC#PARAMS#CURRENTDIR
66
+ currentDirSetting:
67
+ "The directory containing the import path currently being addressed.", // $COMMENT#JSDOC#PARAMS#CURRENTDIRSETTING
68
+ cwdSetting: "The current working directory.", // $COMMENT#JSDOC#PARAMS#CWDSETTING
69
+ visitedSetSetting:
70
+ "The set of strings tracking the import paths that have already been visited.", // $COMMENT#JSDOC#PARAMS#VISITEDSETSETTING
71
+ depthSetting: "The current depth of the recursion.", // $COMMENT#JSDOC#PARAMS#DEPTHSETTING
72
+ maxDepthSetting: "The maximum depth allowed for the recursion.", // $COMMENT#JSDOC#PARAMS#MAXDEPTHSETTING
64
73
  configData:
65
- "The config's data property. (Values are typed `any` given the limitations in typing recursive values in JSDoc.)", // $COMMENT#JSDOC#PARAMS#CONFIGDATA
66
- configDataMap:
67
- "The map housing the flattened keys with their values and sources through recursion, instantiated as a `new Map()`.", // $COMMENT#JSDOC#PARAMS#CONFIGDATAMAP
68
- parentKeys:
69
- "The list of keys that are parent to the key at hand given the recursive nature of the config's data's data structure, instantiated as an empty array of strings.", // $COMMENT#JSDOC#PARAMS#PARENTKEYS
74
+ "The config's data property. (Values are typed `unknown` given the limitations in typing recursive values in JSDoc.)", // $COMMENT#JSDOC#PARAMS#CONFIGDATA
75
+ configDataMapOption:
76
+ "The map housing the flattened keys with their values and sources through recursion, instantiated as a `new Map()`.", // $COMMENT#JSDOC#PARAMS#CONFIGDATAMAPOPTION
77
+ parentKeysOption:
78
+ "The list of keys that are parent to the key at hand given the recursive nature of the config's data's data structure, instantiated as an empty array of strings (`[]`).", // $COMMENT#JSDOC#PARAMS#PARENTKEYSOPTION
70
79
  configPath:
71
- "The path of the config, either from `comments.config.js` or from a config passed via the `--config` flag.", // $COMMENT#JSDOC#PARAMS#CONFIGPATH
80
+ 'The path of the config from `comments.config.js`, or from a config passed via the `--config` flag in the CLI, or from one passed via `"commentVariables.config": true` in `.vscode/settings.json` for the VS Code Extension.', // $COMMENT#JSDOC#PARAMS#CONFIGPATH
81
+ options: "The additional options as follows:", // $COMMENT#JSDOC#PARAMS#OPTIONS
82
+ settings: "The required settings as follows:", // $COMMENT#JSDOC#PARAMS#SETTINGS
83
+ error: "The error object being handle for the logging.", // $COMMENT#JSDOC#PARAMS#ERROR
72
84
  }),
73
85
  returns: Object.freeze({
74
86
  exitDueToFailure:
@@ -78,13 +90,13 @@ const data = {
78
90
  makeRuleCompress:
79
91
  "The compress rule based on the reversed flattened config data.", // $COMMENT#JSDOC#RETURNS#MAKERULECOMPRESS
80
92
  findAllImports:
81
- "The complete set of strings of import paths recursively related to the given file path, or `null` if an issue has arisen.", // $COMMENT#JSDOC#RETURNS#FINDALLIMPORTS
93
+ "The complete set of strings of import paths recursively related to the given file path in a success object (`success: true`). Errors are bubbled up during failures in a failure object (`success: false`).", // $COMMENT#JSDOC#RETURNS#FINDALLIMPORTS
82
94
  processImport:
83
- "`true` to skip unresolved paths, `false` if resolution fails at any level.", // $COMMENT#JSDOC#RETURNS#PROCESSIMPORT
95
+ "`true` to continue to the next operation, `false` to stop the whole `findAllImports` process.", // $COMMENT#JSDOC#RETURNS#PROCESSIMPORT
84
96
  flattenConfigData:
85
- "Both the flattened config data and its reversed version to ensure the strict reversibility of the `resolve` and `compress` commands.", // $COMMENT#JSDOC#RETURNS#FLATTENCONFIGDATA
97
+ "Both the flattened config data and its reversed version to ensure the strict reversibility of the `resolve` and `compress` commands in a success object (`success: true`). Errors are bubbled up during failures so they can be reused differently on the CLI and the VS Code Extension in a failure object (`success: false`).", // $COMMENT#JSDOC#RETURNS#FLATTENCONFIGDATA
86
98
  resolveConfig:
87
- "The flattened config data, the reverse flattened config data, the verified config path, the raw passed ignores, and the original config.", // $COMMENT#JSDOC#RETURNS#RESOLVECONFIG
99
+ "The flattened config data, the reverse flattened config data, the verified config path, the raw passed ignores, and the original config. Errors are returned during failures so they can be reused differently on the CLI and the VS Code Extension.", // $COMMENT#JSDOC#RETURNS#RESOLVECONFIG
88
100
  }),
89
101
  }),
90
102
  };
@@ -18,10 +18,10 @@ export const hasPackageJson = fs.existsSync(path.join(cwd, "package.json"));
18
18
  // to prevent irreversible changes
19
19
  export const hasGitFolder = fs.existsSync(path.join(cwd, ".git"));
20
20
 
21
- // comments.config.js
21
+ // comments.config.js // comment-variables-resolve-config
22
22
  export const defaultConfigFileName = "comments.config.js";
23
23
 
24
- // flags
24
+ // flags // comment-variables-resolve-config
25
25
  export const configFlag = "--config";
26
26
  export const lintConfigImportsFlag = "--lint-config-imports";
27
27
  export const myIgnoresOnlyFlag = "--my-ignores-only";
@@ -29,11 +29,12 @@ export const myIgnoresOnlyFlag = "--my-ignores-only";
29
29
  // ESLint ignores
30
30
  export const knownIgnores = [
31
31
  "node_modules",
32
+ "dist",
33
+ "out",
32
34
  ".next",
33
35
  ".react-router",
34
36
  ".parcel-cache",
35
37
  ".react-router-parcel",
36
- "dist",
37
38
  ];
38
39
 
39
40
  // ESLint file globs
@@ -70,8 +71,21 @@ export const typeScriptAndJSXCompatible = {
70
71
  // messageId
71
72
  export const placeholderMessageId = "placeholderMessageId";
72
73
 
73
- // regexes
74
- export const configKeyRegex = /^[\p{Ll}\p{Lu}\p{Lo}\p{Pd}\p{Pc}\p{N}\s]+$/u;
75
- export const flattenedConfigKeyRegex = /^[\p{Lu}\p{Lo}\p{Pd}\p{Pc}\p{N}#]+$/u; // same as configKeyRegex but without lowercase letters (\p{Ll}), without whitespaces (\s which are replaced by underscores) and with the '#' character (that links each subkey together)
76
- export const flattenedConfigPlaceholderRegex =
77
- /\$COMMENT#([\p{Lu}\p{Lo}\p{Pd}\p{Pc}\p{N}_#]+)/gu; // same as flattenedConfigKeyRegex but taking the prefix $COMMENT# into consideration, removing ^ and $ in the capture group, globally
74
+ // placeholder prefix // comment-variables-resolve-config
75
+ export const $COMMENT = "$COMMENT";
76
+
77
+ // success objects // comment-variables-resolve-config
78
+ export const successFalse = Object.freeze({
79
+ success: false,
80
+ });
81
+ export const successTrue = Object.freeze({
82
+ success: true,
83
+ });
84
+
85
+ // error objects // comment-variables-resolve-config
86
+ export const typeError = Object.freeze({
87
+ type: "error",
88
+ });
89
+ export const typeWarning = Object.freeze({
90
+ type: "warning",
91
+ });
@@ -0,0 +1,13 @@
1
+ import { $COMMENT } from "./bases.js";
2
+
3
+ import { escapeRegex } from "../utilities/helpers.js";
4
+
5
+ // comment-variables-resolve-config
6
+ export const configKeyRegex = /^[\p{Ll}\p{Lu}\p{Lo}\p{Pd}\p{Pc}\p{N}\s]+$/u;
7
+ // comment-variables-resolve-config
8
+ export const flattenedConfigKeyRegex = /^[\p{Lu}\p{Lo}\p{Pd}\p{Pc}\p{N}#]+$/u; // same as configKeyRegex but without lowercase letters (\p{Ll}), without whitespaces (\s which are replaced by underscores) and with the '#' character (that links each subkey together)
9
+ // comment-variables-resolve-config
10
+ export const flattenedConfigPlaceholderRegex = new RegExp(
11
+ `${escapeRegex($COMMENT)}#([\\p{Lu}\\p{Lo}\\p{Pd}\\p{Pc}\\p{N}#_]+)`,
12
+ "gu"
13
+ ); // same as flattenedConfigKeyRegex but taking the prefix $COMMENT and its # into consideration, removing ^ and $ in the capture group, and using _ as replacement for whitesplaces, globally
@@ -1,4 +1,4 @@
1
- import { placeholderMessageId } from "../constants/bases.js";
1
+ import { placeholderMessageId, $COMMENT } from "../constants/bases.js";
2
2
 
3
3
  import { escapeRegex } from "..//utilities/helpers.js";
4
4
 
@@ -49,7 +49,7 @@ const makeRule = (reversedFlattenedConfigData) => {
49
49
 
50
50
  fixedText = fixedText.replace(pattern, () => {
51
51
  modified = true;
52
- return `$COMMENT#${commentKey}`;
52
+ return `${$COMMENT}#${commentKey}`;
53
53
  });
54
54
  }
55
55
 
@@ -1,7 +1,5 @@
1
- import {
2
- placeholderMessageId,
3
- flattenedConfigPlaceholderRegex,
4
- } from "../constants/bases.js";
1
+ import { placeholderMessageId } from "../constants/bases.js";
2
+ import { flattenedConfigPlaceholderRegex } from "../constants/regexes.js";
5
3
 
6
4
  /**
7
5
  * The utility that creates the resolve rule based on the flattened config data, used to transform $COMMENT#* placeholders into actual comments.
@@ -1,11 +1,11 @@
1
1
  import { z } from "zod";
2
2
 
3
- import { configKeyRegex } from "../constants/bases.js";
3
+ import { configKeyRegex } from "../constants/regexes.js";
4
4
 
5
5
  export const ConfigDataSchema = z
6
6
  .lazy(() =>
7
7
  z.record(
8
- z.any().superRefine((val, ctx) => {
8
+ z.unknown().superRefine((val, ctx) => {
9
9
  if (typeof val === "string") {
10
10
  return;
11
11
  }
@@ -4,93 +4,141 @@ import path from "path";
4
4
  import { resolveImportingPath } from "resolve-importing-path";
5
5
  import { getSourceCodeFromFilePath } from "get-sourcecode-from-file-path";
6
6
 
7
- /* findAllImports */
7
+ import { successFalse, successTrue, typeWarning } from "../constants/bases.js";
8
8
 
9
9
  /**
10
- * Processes recursively and resolves a single import path.
10
+ * @typedef {{
11
+ * success: false;
12
+ * errors: Array<{ message: string; type: "warning";}>;
13
+ * } | {
14
+ * success: true;
15
+ * visitedSet: Set<string>;
16
+ * }} FindAllImportsResults
17
+ */
18
+
19
+ // IMPORTANT. findAllImports needs to be able to take a callback function that it can play at every recursion to find the corresponding value for go-to-definitions. But that's on the roadmap, not in the first release. The first implementation of this pinpoint go-to-definition mechanism will be made by analyzing each path obtained rather than by doing so as the paths are being obtained.
20
+
21
+ /**
22
+ * Processes recursively and resolves a single import path. (Unlike `findAllImports`, here `currentDir`, `cwd`, `visitedSet`, `depth`, and `maxDepth` aren't options because they are mandatory and not pre-parameterized.)
11
23
  * @param {string} importPath The import path currently being addressed.
12
- * @param {string} currentDir The directory containing the import path currently being addressed.
13
- * @param {string} cwd The current working directory, set as `process.cwd()` by default.
14
- * @param {Set<string>} visitedSet The set of strings tracking the import paths that have already been visited, instantiated as a `new Set()` by default.
15
- * @param {number} depth The current depth of the recursion, instantiated at `0` by default.
16
- * @param {number} maxDepth The maximum depth allowed for the recursion, instantiated at `100` by default.
17
- * @returns `true` to skip unresolved paths, `false` if resolution fails at any level.
24
+ * @param {Object} settings The required settings as follows:
25
+ * @param {string} settings.currentDir The directory containing the import path currently being addressed.
26
+ * @param {string} settings.cwd The current working directory.
27
+ * @param {Set<string>} settings.visitedSet The set of strings tracking the import paths that have already been visited.
28
+ * @param {number} settings.depth The current depth of the recursion.
29
+ * @param {number} settings.maxDepth The maximum depth allowed for the recursion.
30
+ * @returns `true` to continue to the next operation, `false` to stop the whole `findAllImports` process. //
18
31
  */
19
32
  const processImport = (
20
33
  importPath,
21
- currentDir,
22
- cwd,
23
- visitedSet,
24
- depth,
25
- maxDepth
34
+ { currentDir, cwd, visitedSet, depth, maxDepth }
26
35
  ) => {
36
+ // Resolves the provided import path.
27
37
  const resolvedPath = resolveImportingPath(currentDir, importPath, cwd);
28
- if (!resolvedPath) return true;
38
+ // Returns true early to skip processing on unresolved paths.
39
+ if (!resolvedPath) return { ...successTrue, visitedSet };
29
40
 
30
- const result = findAllImports(
31
- resolvedPath,
41
+ // Establishes the options for the next round of findAllImports.
42
+ const findAllImportsOptions = {
32
43
  cwd,
33
44
  visitedSet,
34
- depth + 1,
35
- maxDepth
45
+ depth: depth + 1,
46
+ maxDepth,
47
+ };
48
+
49
+ // Runs findAllImports on the imported path resolved, thus recursively.
50
+ const findAllImportsResults = /** @type {FindAllImportsResults} */ (
51
+ findAllImports(resolvedPath, findAllImportsOptions)
36
52
  );
37
- return result !== null; // Returns false if child failed.
53
+ // Returns true if the round of findAllImports succeeded, false if it failed.
54
+ return findAllImportsResults;
38
55
  };
39
56
 
40
57
  /**
41
58
  * Finds all import paths recursively related to a given file path.
42
59
  * @param {string} filePath The absolute path of the file whose imports are being recursively found, such as that of a project's `comments.config.js` file.
43
- * @param {string} cwd The current working directory, set as `process.cwd()` by default.
44
- * @param {Set<string>} visitedSet The set of strings tracking the import paths that have already been visited, instantiated as a `new Set()` by default.
45
- * @param {number} depth The current depth of the recursion, instantiated at `0` by default.
46
- * @param {number} maxDepth The maximum depth allowed for the recursion, instantiated at `100` by default.
47
- * @returns The complete set of strings of import paths recursively related to the given file path, or `null` if an issue has arisen.
60
+ * @param {Object} options The additional options as follows:
61
+ * @param {string} [options.cwd] The current working directory, set as `process.cwd()` by default.
62
+ * @param {Set<string>} [options.visitedSet] The current working directory, set as `process.cwd()` by default.
63
+ * @param {number} [options.depth] The current depth of the recursion, instantiated at `0` by default.
64
+ * @param {number} [options.maxDepth] The maximum depth allowed for the recursion, instantiated at `100` by default.
65
+ * @returns The complete set of strings of import paths recursively related to the given file path in a success object (`success: true`). Errors are bubbled up during failures in a failure object (`success: false`).
48
66
  */
49
67
  export const findAllImports = (
50
68
  filePath,
51
- cwd = process.cwd(),
52
- visitedSet = new Set(),
53
- depth = 0,
54
- maxDepth = 100
69
+ {
70
+ cwd = process.cwd(),
71
+ visitedSet = new Set(),
72
+ depth = 0,
73
+ maxDepth = 100,
74
+ } = {}
55
75
  ) => {
56
76
  // Fails early if max depth is recursively reached.
57
77
  if (depth > maxDepth) {
58
- console.error(`ERROR. Max depth ${maxDepth} reached at ${filePath}.`);
59
- return null;
78
+ return {
79
+ ...successFalse,
80
+ errors: [
81
+ {
82
+ ...typeWarning,
83
+ message: `WARNING. Max depth ${maxDepth} reached at ${filePath}.`,
84
+ },
85
+ ],
86
+ };
60
87
  }
61
88
  // Fails early if no file is found.
62
89
  if (!fs.existsSync(filePath)) {
63
- console.error(`ERROR. File not found at ${filePath}.`);
64
- return null;
90
+ return {
91
+ ...successFalse,
92
+ errors: [
93
+ {
94
+ ...typeWarning,
95
+ message: `WARNING. File not found at ${filePath}.`,
96
+ },
97
+ ],
98
+ };
99
+ }
100
+ // Returns the existing set directly if a path has already been visited.
101
+ if (visitedSet.has(filePath)) {
102
+ return { ...successTrue, visitedSet };
65
103
  }
66
104
 
67
105
  // Updates the visited set.
68
- if (visitedSet.has(filePath)) return visitedSet;
69
106
  visitedSet.add(filePath);
70
107
 
71
108
  // Parses the file's source code AST.
72
109
  const sourceCode = getSourceCodeFromFilePath(filePath);
110
+ // Fails early there is no AST.
73
111
  if (!sourceCode?.ast) {
74
- console.error(`ERROR. Failed to parse AST for ${filePath}.`);
75
- return null;
112
+ return {
113
+ ...successFalse,
114
+ errors: [
115
+ {
116
+ ...typeWarning,
117
+ message: `WARNING. Failed to parse AST for ${filePath}.`,
118
+ },
119
+ ],
120
+ };
76
121
  }
77
122
 
123
+ // Makes the joint settings for the conditional calls of processImport.
124
+ const processImportSettings = {
125
+ currentDir: path.dirname(filePath),
126
+ cwd,
127
+ visitedSet,
128
+ depth,
129
+ maxDepth,
130
+ };
131
+
78
132
  // Processes all imports.
79
- const currentDir = path.dirname(filePath);
80
133
  for (const node of sourceCode.ast.body) {
81
134
  // ES Modules (import x from 'y')
82
135
  if (node.type === "ImportDeclaration") {
83
- if (
84
- !processImport(
85
- node.source.value,
86
- currentDir,
87
- cwd,
88
- visitedSet,
89
- depth,
90
- maxDepth
91
- )
92
- ) {
93
- return null;
136
+ const processImportResults = processImport(
137
+ node.source.value,
138
+ processImportSettings
139
+ );
140
+ if (!processImportResults) {
141
+ return processImportResults;
94
142
  }
95
143
  }
96
144
 
@@ -101,20 +149,15 @@ export const findAllImports = (
101
149
  node.expression.callee.name === "require" &&
102
150
  node.expression.arguments[0]?.type === "Literal"
103
151
  ) {
104
- if (
105
- !processImport(
106
- node.expression.arguments[0].value,
107
- currentDir,
108
- cwd,
109
- visitedSet,
110
- depth,
111
- maxDepth
112
- )
113
- ) {
114
- return null;
152
+ const processImportResults = processImport(
153
+ node.expression.arguments[0].value,
154
+ processImportSettings
155
+ );
156
+ if (!processImportResults) {
157
+ return processImportResults;
115
158
  }
116
159
  }
117
160
  }
118
161
 
119
- return visitedSet; // success
162
+ return { ...successTrue, visitedSet };
120
163
  };
@@ -1,18 +1,30 @@
1
- import { flattenedConfigKeyRegex } from "../constants/bases.js";
1
+ import { successFalse, successTrue, typeError } from "../constants/bases.js";
2
+ import { flattenedConfigKeyRegex } from "../constants/regexes.js";
2
3
 
3
- import { exitDueToFailure } from "../utilities/helpers.js";
4
+ /**
5
+ * @typedef {Record<string, unknown>} ConfigData
6
+ *
7
+ * @typedef {{
8
+ * success: false;
9
+ * errors: Array<{ type: "error" | "warning"; message: string }>;
10
+ * } | {
11
+ * success: true;
12
+ * flattenedConfigData: Record<string, string>;
13
+ * reversedFlattenedConfigData: Record<string, string>;
14
+ * }} FlattenConfigDataResults
15
+ */
4
16
 
5
17
  /**
6
18
  * Flattens the config's data property into a one-dimensional object of $COMMENT-*-like keys and string values.
7
- * @param {Record<string, any>} configData The config's data property. (Values are typed `any` given the limitations in typing recursive values in JSDoc.)
8
- * @param {Map<string, {value: string; source: string}>} configDataMap The map housing the flattened keys with their values and sources through recursion, instantiated as a `new Map()`.
9
- * @param {string[]} parentKeys The list of keys that are parent to the key at hand given the recursive nature of the config's data's data structure, instantiated as an empty array of strings.
10
- * @returns Both the flattened config data and its reversed version to ensure the strict reversibility of the `resolve` and `compress` commands.
19
+ * @param {ConfigData} configData The config's data property. (Values are typed `unknown` given the limitations in typing recursive values in JSDoc.)
20
+ * @param {Object} [options] The additional options as follows:
21
+ * @param {Map<string, {value: string; source: string}>} [options.configDataMap] The map housing the flattened keys with their values and sources through recursion, instantiated as a `new Map()`.
22
+ * @param {string[]} [options.parentKeys] The list of keys that are parent to the key at hand given the recursive nature of the config's data's data structure, instantiated as an empty array of strings (`[]`).
23
+ * @returns Both the flattened config data and its reversed version to ensure the strict reversibility of the `resolve` and `compress` commands in a success object (`success: true`). Errors are bubbled up during failures so they can be reused differently on the CLI and the VS Code Extension in a failure object (`success: false`).
11
24
  */
12
25
  export const flattenConfigData = (
13
26
  configData,
14
- configDataMap = new Map(),
15
- parentKeys = []
27
+ { configDataMap = new Map(), parentKeys = [] } = {}
16
28
  ) => {
17
29
  for (const [key, value] of Object.entries(configData)) {
18
30
  const newKeys = [...parentKeys, key];
@@ -24,12 +36,18 @@ export const flattenConfigData = (
24
36
 
25
37
  if (typeof value === "string") {
26
38
  if (configDataMap.has(normalizedKey)) {
27
- console.error(
28
- `ERROR. The normalized key "${normalizedKey}" has already been assigned. Check between the two following key paths: \n"${
29
- configDataMap.get(normalizedKey).source
30
- }" \n"${source}"`
31
- );
32
- exitDueToFailure();
39
+ // checks the uniqueness of each normalized key
40
+ return {
41
+ ...successFalse,
42
+ errors: [
43
+ {
44
+ ...typeError,
45
+ message: `ERROR. The normalized key "${normalizedKey}" has already been assigned. Check between the two following key paths: \n"${
46
+ configDataMap.get(normalizedKey).source
47
+ }" \n"${source}"`,
48
+ },
49
+ ],
50
+ };
33
51
  }
34
52
 
35
53
  configDataMap.set(normalizedKey, {
@@ -37,10 +55,13 @@ export const flattenConfigData = (
37
55
  source,
38
56
  });
39
57
  } else if (typeof value === "object" && value && !Array.isArray(value)) {
40
- /** @type {Record<string, any>} */
41
- const typedValue = value;
58
+ const subConfigData = /** @type {ConfigData} */ (value);
59
+ const flattenConfigDataOptions = { configDataMap, parentKeys: newKeys };
42
60
 
43
- flattenConfigData(typedValue, configDataMap, newKeys);
61
+ const flattenConfigDataResults = /** @type {FlattenConfigDataResults} */ (
62
+ flattenConfigData(subConfigData, flattenConfigDataOptions)
63
+ );
64
+ if (!flattenConfigDataResults.success) return flattenConfigDataResults;
44
65
  }
45
66
  }
46
67
 
@@ -63,36 +84,52 @@ export const flattenConfigData = (
63
84
  const flattenedConfigDataValuesArray = Object.values(flattenedConfigData);
64
85
  const flattenedConfigDataValuesSet = new Set(flattenedConfigDataValuesArray);
65
86
 
66
- flattenedConfigDataKeysSet.forEach((key) => {
67
- // checks the reversability of flattenedConfigData
87
+ for (const key of flattenedConfigDataKeysSet) {
68
88
  if (flattenedConfigDataValuesSet.has(key)) {
69
- console.error(
70
- `ERROR. The key "${key}" is and shouldn't be among the values of flattenedConfigData.`
71
- );
72
- exitDueToFailure();
89
+ // checks the reversability of flattenedConfigData
90
+ return {
91
+ ...successFalse,
92
+ errors: [
93
+ {
94
+ ...typeError,
95
+ message: `ERROR. The key "${key}" is and shouldn't be among the values of flattenedConfigData.`,
96
+ },
97
+ ],
98
+ };
73
99
  }
74
100
  if (!flattenedConfigKeyRegex.test(key)) {
75
101
  // checks if each key for flattenedConfigData passes the flattenedConfigKeyRegex test
76
- console.error(
77
- `ERROR. Somehow the key "${key}" is not properly formatted. (This is mostly an internal mistake.)`
78
- );
79
- exitDueToFailure();
102
+ return {
103
+ ...successFalse,
104
+ errors: [
105
+ {
106
+ ...typeError,
107
+ message: `ERROR. Somehow the key "${key}" is not properly formatted. (This is mostly an internal mistake.)`,
108
+ },
109
+ ],
110
+ };
80
111
  }
81
- });
112
+ }
82
113
 
83
114
  /** @type {Set<string>} */
84
115
  const set = new Set();
85
116
 
86
- flattenedConfigDataValuesArray.forEach((value) => {
117
+ for (const value of flattenedConfigDataValuesArray) {
87
118
  if (set.has(value)) {
119
+ console.log("errors, duplicate value");
88
120
  // checks that no two values are duplicate
89
- console.error(
90
- `ERROR. The value "${value}" is already assigned to an existing key.`
91
- );
92
- exitDueToFailure();
121
+ return {
122
+ ...successFalse,
123
+ errors: [
124
+ {
125
+ ...typeError,
126
+ message: `ERROR. The value "${value}" is already assigned to an existing key.`,
127
+ },
128
+ ],
129
+ };
93
130
  }
94
131
  set.add(value);
95
- });
132
+ }
96
133
 
97
134
  // Also including the reversed flattened config data.
98
135
 
@@ -101,6 +138,7 @@ export const flattenConfigData = (
101
138
  );
102
139
 
103
140
  return {
141
+ ...successTrue,
104
142
  flattenedConfigData,
105
143
  reversedFlattenedConfigData,
106
144
  };
@@ -6,7 +6,25 @@
6
6
  */
7
7
  export const exitDueToFailure = () => process.exit(1);
8
8
 
9
- /* escapeRegex */
9
+ /**
10
+ * Logs an error to the console depending on its type. (`"error"` or `"warning"`.)
11
+ * @param {{type: "error" | "warning"; message: string}} error The error object being handle for the logging.
12
+ */
13
+ export const logError = (error) => {
14
+ switch (error.type) {
15
+ case "error":
16
+ console.error(error.message);
17
+ break;
18
+ case "warning":
19
+ console.warn(error.message);
20
+ break;
21
+ default:
22
+ console.error("ERROR. Error type unrecognized.");
23
+ break;
24
+ }
25
+ };
26
+
27
+ /* escapeRegex */ // comment-variables-resolve-config
10
28
 
11
29
  /**
12
30
  * Escapes all regex characters with a `"\"` in a string to prepare it for use in a regex.
@@ -1,5 +1,7 @@
1
- import { existsSync } from "fs";
2
- import { pathToFileURL } from "url";
1
+ import fs from "fs";
2
+ import url from "url";
3
+
4
+ import { successFalse, typeError } from "../constants/bases.js";
3
5
 
4
6
  import { flattenConfigData } from "./flatten-config-data.js";
5
7
 
@@ -7,39 +9,64 @@ import { ConfigDataSchema, ConfigIgnoresSchema } from "../schemas/config.js";
7
9
 
8
10
  /**
9
11
  * Verifies, validates and resolves the config path to retrieve the config's data and ignores.
10
- * @param {string} configPath The path of the config, either from `comments.config.js` or from a config passed via the `--config` flag.
11
- * @returns The flattened config data, the reverse flattened config data, the verified config path, the raw passed ignores, and the original config.
12
+ * @param {string} configPath The path of the config from `comments.config.js`, or from a config passed via the `--config` flag in the CLI, or from one passed via `"commentVariables.config": true` in `.vscode/settings.json` for the VS Code Extension.
13
+ * @returns The flattened config data, the reverse flattened config data, the verified config path, the raw passed ignores, and the original config. Errors are returned during failures so they can be reused differently on the CLI and the VS Code Extension.
12
14
  */
13
15
  export async function resolveConfig(configPath) {
14
16
  // Step 1: Checks if config file exists
15
17
 
16
- if (!existsSync(configPath)) {
17
- console.warn("No config file found. Exiting gracefully.");
18
- return null;
18
+ if (!fs.existsSync(configPath)) {
19
+ return {
20
+ ...successFalse,
21
+ errors: [
22
+ {
23
+ ...typeError,
24
+ message: "ERROR. No config file found.",
25
+ },
26
+ ],
27
+ };
19
28
  }
20
29
 
21
30
  // Step 2: Imports the config dynamically
22
31
 
23
- const configModule = await import(pathToFileURL(configPath));
24
- const config = configModule.default;
32
+ const configModule = /** @type {unknown} */ (
33
+ await import(url.pathToFileURL(configPath))
34
+ );
35
+ const config = /** @type {unknown} */ (configModule.default);
25
36
 
26
37
  // Step 3: Validates config object
27
38
 
28
39
  // validates config
29
40
  if (!config || typeof config !== "object" || Array.isArray(config)) {
30
- console.warn(
31
- "Invalid config format. The config should be an object. Exiting."
32
- );
33
- return null;
41
+ return {
42
+ ...successFalse,
43
+ errors: [
44
+ {
45
+ ...typeError,
46
+ message:
47
+ "ERROR. Invalid config format. The config should be an object.",
48
+ },
49
+ ],
50
+ };
34
51
  }
35
52
 
36
53
  // validates config.data
37
54
  const configDataResult = ConfigDataSchema.safeParse(config.data);
38
55
 
39
56
  if (!configDataResult.success) {
40
- console.warn("Config data could not pass validation from zod.");
41
- configDataResult.error.errors.map((e) => console.log(e.message));
42
- return null;
57
+ return {
58
+ ...successFalse,
59
+ errors: [
60
+ {
61
+ ...typeError,
62
+ message: "ERROR. Config data could not pass validation from zod.",
63
+ },
64
+ ...configDataResult.error.errors.map((e) => ({
65
+ ...typeError,
66
+ message: e.message,
67
+ })),
68
+ ],
69
+ };
43
70
  }
44
71
 
45
72
  // validates config.ignores
@@ -48,9 +75,25 @@ export async function resolveConfig(configPath) {
48
75
  );
49
76
 
50
77
  if (!configIgnoresSchemaResult.success) {
51
- console.warn("Config ignores could not pass validation from zod.");
52
- configIgnoresSchemaResult.error.errors.map((e) => console.log(e.message));
53
- return null;
78
+ return {
79
+ ...successFalse,
80
+ errors: [
81
+ {
82
+ ...typeError,
83
+ message: "ERROR. Config ignores could not pass validation from zod.",
84
+ },
85
+ ...configIgnoresSchemaResult.error.errors.map((e) => ({
86
+ ...typeError,
87
+ message: e.message,
88
+ })),
89
+ ],
90
+ };
91
+ }
92
+
93
+ const flattenedConfigDataResults = flattenConfigData(configDataResult.data);
94
+
95
+ if (!flattenedConfigDataResults.success) {
96
+ return flattenedConfigDataResults;
54
97
  }
55
98
 
56
99
  // sends back:
@@ -59,7 +102,7 @@ export async function resolveConfig(configPath) {
59
102
  // - the verified config path
60
103
  // - and the raw passed ignores
61
104
  return {
62
- ...flattenConfigData(configDataResult.data), // finalized
105
+ ...flattenedConfigDataResults, // finalized
63
106
  configPath, // finalized
64
107
  passedIgnores: configIgnoresSchemaResult.data, // addressed with --lint-config-imports and --my-ignores-only to be finalized
65
108
  config, // and the config itself too
package/library/index.js CHANGED
@@ -7,18 +7,18 @@ import {
7
7
  cwd,
8
8
  hasPackageJson,
9
9
  hasGitFolder,
10
- defaultConfigFileName,
11
- configFlag,
12
- lintConfigImportsFlag,
13
- myIgnoresOnlyFlag,
14
- knownIgnores,
10
+ defaultConfigFileName, // shared
11
+ configFlag, // shared
12
+ lintConfigImportsFlag, // shared
13
+ myIgnoresOnlyFlag, // shared
14
+ knownIgnores, // shared
15
15
  resolveRuleName,
16
16
  compressRuleName,
17
17
  } from "./_commons/constants/bases.js";
18
18
 
19
- import { exitDueToFailure } from "./_commons/utilities/helpers.js";
20
- import { resolveConfig } from "./_commons/utilities/resolve-config.js";
21
- import { findAllImports } from "./_commons/utilities/find-all-imports.js";
19
+ import { exitDueToFailure, logError } from "./_commons/utilities/helpers.js";
20
+ import { resolveConfig } from "./_commons/utilities/resolve-config.js"; // shared
21
+ import { findAllImports } from "./_commons/utilities/find-all-imports.js"; // own package
22
22
  import {
23
23
  resolveCommentsFlow,
24
24
  compressCommentsFlow,
@@ -61,8 +61,9 @@ const passedConfigPath =
61
61
  // defaults to comments.config.js if no --config flag is set
62
62
  const rawConfigPath = passedConfigPath ?? path.join(cwd, defaultConfigFileName);
63
63
 
64
- const results = await resolveConfig(rawConfigPath);
65
- if (!results) {
64
+ const resolveConfigResults = await resolveConfig(rawConfigPath);
65
+ if (!resolveConfigResults.success) {
66
+ resolveConfigResults.errors.forEach((e) => logError(e));
66
67
  exitDueToFailure();
67
68
  }
68
69
 
@@ -72,21 +73,36 @@ const {
72
73
  reversedFlattenedConfigData,
73
74
  configPath,
74
75
  passedIgnores,
75
- } = results;
76
+ } = resolveConfigResults;
76
77
 
77
78
  skipDetails || console.log("Running with config:", config);
78
- skipDetails || console.log("Flattened config is:", flattenedConfigData);
79
+ skipDetails || console.log("Flattened config data is:", flattenedConfigData);
79
80
  skipDetails ||
80
- console.log("Reversed flattened config is:", reversedFlattenedConfigData);
81
+ console.log(
82
+ "Reversed flattened config data is:",
83
+ reversedFlattenedConfigData
84
+ );
81
85
  skipDetails || console.log("Config path is:", configPath);
82
86
  skipDetails || console.log("Passed ignores are:", passedIgnores);
83
87
 
84
88
  // ADDRESSES THE --lint-config-imports FLAG, GIVEN THAT THE FILES IMPORTED BY THE CONFIG ARE IGNORED BY DEFAULT.
85
89
 
86
90
  const lintConfigImports = commands.indexOf(lintConfigImportsFlag) >= 2;
87
- const rawConfigPathIgnores = lintConfigImports
88
- ? [configPath]
89
- : [...(findAllImports(configPath) ?? [])];
91
+ let rawConfigPathIgnores = [configPath];
92
+
93
+ if (!lintConfigImports) {
94
+ const findAllImportsResults = findAllImports(configPath);
95
+ if (!findAllImportsResults.success) {
96
+ findAllImportsResults.errors.forEach((e) => logError(e));
97
+ console.warn(
98
+ "Defaulting to --lint-config-imports flag behavior, not ignoring config path imports, only the config path itself."
99
+ );
100
+ } else {
101
+ rawConfigPathIgnores = [...findAllImportsResults.visitedSet] ?? [
102
+ configPath,
103
+ ];
104
+ }
105
+ }
90
106
 
91
107
  // the ignore paths must be relative
92
108
  const configPathIgnores = rawConfigPathIgnores.map((e) =>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "comment-variables",
3
- "version": "0.4.2",
3
+ "version": "0.6.0",
4
4
  "description": "A CLI tool for configuring, managing and maintaining JavaScript comments as JavaScript variables.",
5
5
  "bin": {
6
6
  "jscomments": "./library/index.js",
@@ -27,8 +27,8 @@
27
27
  "dependencies": {
28
28
  "@eslint/markdown": "^6.5.0",
29
29
  "eslint": "^9.29.0",
30
- "get-sourcecode-from-file-path": "^1.0.1",
31
- "resolve-importing-path": "^1.0.2",
30
+ "get-sourcecode-from-file-path": "^1.1.2",
31
+ "resolve-importing-path": "^1.0.3",
32
32
  "tsconfig-paths": "^4.2.0",
33
33
  "typescript-eslint": "^8.34.1",
34
34
  "zod": "^3.25.67"