comment-variables 0.1.0 → 0.3.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.
@@ -1,6 +1,5 @@
1
- import { test } from "./import.js";
2
-
3
- const config = {
1
+ const data = {
2
+ // for testing
4
3
  levelOne: {
5
4
  levelTwo: {
6
5
  levelThree: "Level three.",
@@ -11,14 +10,94 @@ const config = {
11
10
  levelThreeAlso: "Also level three here.",
12
11
  levelThreeToo: "This too is level three.",
13
12
  // test: "LEVELONE#LEVELTWO#LEVELTHREE", // errors
14
- [`level$Three#First
15
- whitespace`]: `This is level three
16
- with whitespaces. `,
17
- testing: 2,
13
+ // [`level$Three#First
14
+ // whitespace`]: `This is level three
15
+ // with whitespaces. `, // fails
16
+ // testing: 2, // fails
17
+ // ".'e": "",
18
18
  },
19
19
  },
20
+ // for deving
21
+ jsDoc: Object.freeze({
22
+ definitions: Object.freeze({
23
+ exitDueToFailure:
24
+ "Terminates the whole process with a 'failure' code (1).",
25
+ escapeRegex: `Escapes all regex characters with a \`"\\"\` in a string to prepare it for use in a regex.`,
26
+ makeRuleResolve:
27
+ "The utility that creates the resolve rule based on the flattened config data, used to transform $COMMENT#* placeholders into actual comments.",
28
+ makeRuleCompress:
29
+ "The utility that creates the compress rule based on the reversed flattened config data, used to transform actual comments into $COMMENT#* placeholders.",
30
+ coreCommentsFlow:
31
+ "The core flow at the heart of resolving and compressing comments.",
32
+ resolveCommentsFlow:
33
+ "The flow that resolves $COMMENT#* placeholders intro actual comments.",
34
+ compressCommentsFlow:
35
+ "The flow that compresses actual comments into $COMMENT#* placeholders.",
36
+ findAllImports:
37
+ "Finds all import paths recursively related to a given file path.",
38
+ processImport: "Processes recursively and resolves a single import path.",
39
+ flattenConfigData:
40
+ "Flattens the config's data property into a one-dimensional object of $COMMENT-*-like keys and string values.",
41
+ resolveConfig:
42
+ "Verifies, validates and resolves the config path to retrieve the config's data and ignores.",
43
+ }),
44
+ params: Object.freeze({
45
+ string: "The string.",
46
+ flattenedConfigData:
47
+ "The flattened config data, with $COMMENT#* placeholders as keys and actual comments as values.",
48
+ reversedFlattenedConfigData:
49
+ "The reversed flattened config data, with actual comments as keys and $COMMENT#* placeholders as values.",
50
+ ruleName:
51
+ 'The name of the rule currently used. (Either `"resolve"` or `"compress"`.)',
52
+ ignores:
53
+ "The array of paths and globs for the flow's ESLint instance to ignore.",
54
+ eitherFlattenedConfigData:
55
+ "Either the flattened config data or the reversed flattened config data, since they share the same structure.",
56
+ filePath:
57
+ "The absolute path of the file whose imports are being recursively found, such as that of a project's `comments.config.js` file.",
58
+ cwd: "The current working directory, set as `process.cwd()` by default.",
59
+ visitedSet:
60
+ "The set of strings tracking the import paths that have already been visited, instantiated as a `new Set()` by default.",
61
+ depth:
62
+ "The current depth of the recursion, instantiated at `0` by default.",
63
+ maxDepth:
64
+ "The maximum depth allowed for the recursion, instantiated at `100` by default.",
65
+ importPath: "The import path currently being addressed.",
66
+ currentDir:
67
+ "The directory containing the import path currently being addressed.",
68
+ configData:
69
+ "The config's data property. (Values are typed `any` given the limitations in typing recursive values in JSDoc.)",
70
+ configDataMap:
71
+ "The map housing the flattened keys with their values and sources through recursion, instantiated as a `new Map()`.",
72
+ parentKeys:
73
+ "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.",
74
+ configPath:
75
+ "The path of the config, either from `comments.config.js` or from a config passed via the `--config` flag.",
76
+ }),
77
+ returns: Object.freeze({
78
+ exitDueToFailure:
79
+ "Never. (Somehow typing needs to be explicit for unreachable code inference.)",
80
+ escapeRegex: `The string with regex characters escaped.`,
81
+ makeRuleResolve: "The resolve rule based on the flattened config data.",
82
+ makeRuleCompress:
83
+ "The compress rule based on the reversed flattened config data.",
84
+ findAllImports:
85
+ "The complete set of strings of import paths recursively related to the given file path, or `null` if an issue has arisen.",
86
+ processImport:
87
+ "`true` to skip unresolved paths, `false` if resolution fails at any level.",
88
+ flattenConfigData:
89
+ "Both the flattened config data and its reversed version to ensure the strict reversibility of the `resolve` and `compress` commands.",
90
+ resolveConfig:
91
+ "The flattened config data, the reverse flattened config data, the verified config path and the raw passed ignores.",
92
+ }),
93
+ }),
20
94
  };
21
95
 
22
- export default config;
96
+ const ignores = ["chocolat.js"];
23
97
 
24
- // This too is level three.
98
+ const config = {
99
+ data,
100
+ ignores,
101
+ };
102
+
103
+ export default config;
@@ -0,0 +1,77 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ import tseslint from "typescript-eslint";
5
+
6
+ // plugin name
7
+ export const commentVariablesPluginName = "comment-variables";
8
+
9
+ // rule names
10
+ export const resolveRuleName = "resolve";
11
+ export const compressRuleName = "compress";
12
+
13
+ // current working directory
14
+ export const cwd = process.cwd();
15
+
16
+ // to prevent accidental changes
17
+ export const hasPackageJson = fs.existsSync(path.join(cwd, "package.json"));
18
+ // to prevent irreversible changes
19
+ export const hasGitFolder = fs.existsSync(path.join(cwd, ".git"));
20
+
21
+ // comments.config.js
22
+ export const defaultConfigFileName = "comments.config.js";
23
+
24
+ // flags
25
+ export const configFlag = "--config";
26
+ export const lintConfigImportsFlag = "--lint-config-imports";
27
+ export const myIgnoresOnlyFlag = "--my-ignores-only";
28
+
29
+ // ESLint ignores
30
+ export const knownIgnores = [
31
+ ".next",
32
+ ".react-router",
33
+ "node_modules",
34
+ ".parcel-cache",
35
+ ".react-router-parcel",
36
+ "dist",
37
+ ];
38
+
39
+ // ESLint file globs
40
+ export const allJSTSFileGlobs = [
41
+ "**/*.js",
42
+ "**/*.jsx",
43
+ "**/*.ts",
44
+ "**/*.tsx",
45
+ "**/*.mjs",
46
+ "**/*.cjs",
47
+ ];
48
+ export const allMDFileGlobs = ["**/*.md"];
49
+ export const allMDVirtualJSTSFileGlobs = [
50
+ "**/*.md/*.js",
51
+ "**/*.md/*.jsx",
52
+ "**/*.md/*.ts",
53
+ "**/*.md/*.tsx",
54
+ "**/*.md/*.cjs",
55
+ "**/*.md/*.mjs",
56
+ ];
57
+
58
+ // default ESLint config language options
59
+ export const typeScriptAndJSXCompatible = {
60
+ // for compatibility with TypeScript (.ts and .tsx)
61
+ parser: tseslint.parser,
62
+ // for compatibility with JSX (React, etc.)
63
+ parserOptions: {
64
+ ecmaFeatures: {
65
+ jsx: true,
66
+ },
67
+ },
68
+ };
69
+
70
+ // messageId
71
+ export const placeholderMessageId = "placeholderMessageId";
72
+
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
@@ -0,0 +1,9 @@
1
+ import { resolveRuleName, compressRuleName } from "../constants/bases.js";
2
+
3
+ import makeResolveRule from "../rules/resolve.js";
4
+ import makeCompressRule from "../rules/compress.js";
5
+
6
+ export const ruleNames_makeRules = Object.freeze({
7
+ [resolveRuleName]: makeResolveRule,
8
+ [compressRuleName]: makeCompressRule,
9
+ });
@@ -0,0 +1,78 @@
1
+ import { placeholderMessageId } from "../constants/bases.js";
2
+
3
+ import { escapeRegex } from "..//utilities/helpers.js";
4
+
5
+ /**
6
+ * The utility that creates the compress rule based on the reversed flattened config data, used to transform actual comments into $COMMENT#* placeholders.
7
+ * @param {{[key: string]: string}} reversedFlattenedConfigData The reversed flattened config data, with actual comments as keys and $COMMENT#* placeholders as values.
8
+ * @returns The compress rule based on the reversed flattened config data.
9
+ */
10
+ const makeRule = (reversedFlattenedConfigData) => {
11
+ // Turns the whole reversedFlattenedConfig from an object to an array of key-value arrays sorted by the descending length of each key to prevent partial replacements.
12
+ const sortedReversedFlattenedConfigData = Object.entries(
13
+ reversedFlattenedConfigData
14
+ ).sort(([a], [b]) => b.length - a.length);
15
+
16
+ /** @type {import('@typescript-eslint/utils').TSESLint.RuleModule<typeof placeholderMessageId, []>} */
17
+ const rule = {
18
+ meta: {
19
+ type: "suggestion",
20
+ docs: {
21
+ description:
22
+ "Compresses comments into $COMMENT#* placeholder(s) in comment line(s) or block(s).",
23
+ },
24
+ messages: {
25
+ [placeholderMessageId]:
26
+ "Compressed comments into $COMMENT#* placeholder(s) in comment line(s) or block(s).",
27
+ },
28
+ fixable: "code",
29
+ schema: [],
30
+ },
31
+ create: (context) => {
32
+ const sourceCode = context.sourceCode;
33
+ const comments = sourceCode
34
+ .getAllComments()
35
+ .filter((e) => e.type !== "Shebang");
36
+
37
+ for (const comment of comments) {
38
+ let fixedText = comment.value;
39
+ let modified = false;
40
+
41
+ for (const [
42
+ resolvedValue,
43
+ commentKey,
44
+ ] of sortedReversedFlattenedConfigData) {
45
+ const pattern = new RegExp(
46
+ `(?<=\\s|^)${escapeRegex(resolvedValue)}(?=\\s|$)`,
47
+ "g"
48
+ );
49
+
50
+ fixedText = fixedText.replace(pattern, () => {
51
+ modified = true;
52
+ return `$COMMENT#${commentKey}`;
53
+ });
54
+ }
55
+
56
+ if (modified && fixedText !== comment.value) {
57
+ context.report({
58
+ loc: comment.loc,
59
+ messageId: placeholderMessageId,
60
+ fix(fixer) {
61
+ const fullCommentText =
62
+ comment.type === "Block"
63
+ ? `/*${fixedText}*/`
64
+ : `//${fixedText}`;
65
+ return fixer.replaceText(comment, fullCommentText);
66
+ },
67
+ });
68
+ }
69
+ }
70
+
71
+ return {};
72
+ },
73
+ };
74
+
75
+ return rule;
76
+ };
77
+
78
+ export default makeRule; // compress
@@ -0,0 +1,77 @@
1
+ import {
2
+ placeholderMessageId,
3
+ flattenedConfigPlaceholderRegex,
4
+ } from "../constants/bases.js";
5
+
6
+ /**
7
+ * The utility that creates the resolve rule based on the flattened config data, used to transform $COMMENT#* placeholders into actual comments.
8
+ * @param {{[key: string]: string}} flattenedConfigData The flattened config data, with $COMMENT#* placeholders as keys and actual comments as values.
9
+ * @returns The resolve rule based on the flattened config data.
10
+ */
11
+ const makeRule = (flattenedConfigData) => {
12
+ /** @type {import('@typescript-eslint/utils').TSESLint.RuleModule<typeof placeholderMessageId, []>} */
13
+ const rule = {
14
+ meta: {
15
+ type: "suggestion",
16
+ docs: {
17
+ description:
18
+ "Resolves $COMMENT#* placeholder(s) in comment line(s) or block(s).",
19
+ },
20
+ messages: {
21
+ [placeholderMessageId]:
22
+ "Resolved $COMMENT#* placeholder(s) in comment line(s) or block(s).",
23
+ },
24
+ fixable: "code",
25
+ schema: [],
26
+ },
27
+ create: (context) => {
28
+ const sourceCode = context.sourceCode;
29
+ const comments = sourceCode
30
+ .getAllComments()
31
+ .filter((e) => e.type !== "Shebang");
32
+
33
+ for (const comment of comments) {
34
+ const matches = [
35
+ ...comment.value.matchAll(flattenedConfigPlaceholderRegex),
36
+ ];
37
+
38
+ if (matches.length === 0) continue;
39
+
40
+ let fixedText = comment.value;
41
+ let hasValidFix = false;
42
+
43
+ for (const match of matches) {
44
+ const fullMatch = match[0]; // e.g. $COMMENT#LEVELONE#LEVELTWO
45
+ const key = match[1]; // e.g. LEVELONE#LEVELTWO
46
+ const replacement = flattenedConfigData[key];
47
+
48
+ if (replacement) {
49
+ fixedText = fixedText.replace(fullMatch, replacement);
50
+ hasValidFix = true;
51
+ }
52
+ }
53
+
54
+ if (hasValidFix && fixedText !== comment.value) {
55
+ context.report({
56
+ loc: comment.loc,
57
+ messageId: placeholderMessageId,
58
+ fix(fixer) {
59
+ const range = comment.range;
60
+ const prefix = comment.type === "Block" ? "/*" : "//";
61
+ const suffix = comment.type === "Block" ? "*/" : "";
62
+ const newComment = `${prefix}${fixedText}${suffix}`;
63
+
64
+ return fixer.replaceTextRange(range, newComment);
65
+ },
66
+ });
67
+ }
68
+ }
69
+
70
+ return {};
71
+ },
72
+ };
73
+
74
+ return rule;
75
+ };
76
+
77
+ export default makeRule; // resolve
@@ -0,0 +1,67 @@
1
+ import { z } from "zod";
2
+
3
+ import { configKeyRegex } from "../constants/bases.js";
4
+
5
+ export const ConfigDataSchema = z
6
+ .lazy(() =>
7
+ z.record(
8
+ z.any().superRefine((val, ctx) => {
9
+ if (typeof val === "string") {
10
+ return;
11
+ }
12
+
13
+ if (typeof val === "object" && val && !Array.isArray(val)) {
14
+ const parsed = ConfigDataSchema.safeParse(val);
15
+ if (!parsed.success) {
16
+ for (const issue of parsed.error.issues) {
17
+ ctx.addIssue({
18
+ ...issue,
19
+ path: [...ctx.path, ...issue.path],
20
+ });
21
+ }
22
+ }
23
+ return;
24
+ }
25
+
26
+ ctx.addIssue({
27
+ code: z.ZodIssueCode.custom,
28
+ message: `Value \`${val}\` of type "${typeof val}" should be a string or a nested object.`,
29
+ path: ctx.path,
30
+ });
31
+ })
32
+ )
33
+ )
34
+ .superRefine((obj, ctx) => {
35
+ for (const key of Object.keys(obj)) {
36
+ if (key.includes("$")) {
37
+ ctx.addIssue({
38
+ code: z.ZodIssueCode.custom,
39
+ message: `Key "${key}" should not include the "$" character.`,
40
+ path: [key],
41
+ });
42
+ }
43
+ if (key.includes("#")) {
44
+ ctx.addIssue({
45
+ code: z.ZodIssueCode.custom,
46
+ message: `Key "${key}" should not include the "#" character.`,
47
+ path: [key],
48
+ });
49
+ }
50
+ if (!configKeyRegex.test(key)) {
51
+ ctx.addIssue({
52
+ code: z.ZodIssueCode.custom,
53
+ message: `Key "${key}" should only include whitespaces (s), lowercase letters (Ll), uppercase letters (Lu), other letters (Lo), numbers (N), dash punctuation (Pd), and connector punctuation (Pc).`,
54
+ path: [key],
55
+ });
56
+ }
57
+ }
58
+ });
59
+
60
+ export const ConfigIgnoresSchema = z.array(
61
+ z.string({
62
+ message: `The config's "ignores" key array should be made of string or be empty.`,
63
+ }),
64
+ {
65
+ message: `The config's "ignores" key value should be an array of strings (or at the very least an empty array).`,
66
+ }
67
+ );
@@ -0,0 +1,120 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ import { resolveImportingPath } from "resolve-importing-path";
5
+ import { getSourceCodeFromFilePath } from "get-sourcecode-from-file-path";
6
+
7
+ /* findAllImports */
8
+
9
+ /**
10
+ * Processes recursively and resolves a single import path.
11
+ * @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.
18
+ */
19
+ const processImport = (
20
+ importPath,
21
+ currentDir,
22
+ cwd,
23
+ visitedSet,
24
+ depth,
25
+ maxDepth
26
+ ) => {
27
+ const resolvedPath = resolveImportingPath(currentDir, importPath, cwd);
28
+ if (!resolvedPath) return true;
29
+
30
+ const result = findAllImports(
31
+ resolvedPath,
32
+ cwd,
33
+ visitedSet,
34
+ depth + 1,
35
+ maxDepth
36
+ );
37
+ return result !== null; // Returns false if child failed.
38
+ };
39
+
40
+ /**
41
+ * Finds all import paths recursively related to a given file path.
42
+ * @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.
48
+ */
49
+ export const findAllImports = (
50
+ filePath,
51
+ cwd = process.cwd(),
52
+ visitedSet = new Set(),
53
+ depth = 0,
54
+ maxDepth = 100
55
+ ) => {
56
+ // Fails early if max depth is recursively reached.
57
+ if (depth > maxDepth) {
58
+ console.error(`ERROR. Max depth ${maxDepth} reached at ${filePath}.`);
59
+ return null;
60
+ }
61
+ // Fails early if no file is found.
62
+ if (!fs.existsSync(filePath)) {
63
+ console.error(`ERROR. File not found at ${filePath}.`);
64
+ return null;
65
+ }
66
+
67
+ // Updates the visited set.
68
+ if (visitedSet.has(filePath)) return visitedSet;
69
+ visitedSet.add(filePath);
70
+
71
+ // Parses the file's source code AST.
72
+ const sourceCode = getSourceCodeFromFilePath(filePath);
73
+ if (!sourceCode?.ast) {
74
+ console.error(`ERROR. Failed to parse AST for ${filePath}.`);
75
+ return null;
76
+ }
77
+
78
+ // Processes all imports.
79
+ const currentDir = path.dirname(filePath);
80
+ for (const node of sourceCode.ast.body) {
81
+ // ES Modules (import x from 'y')
82
+ 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;
94
+ }
95
+ }
96
+
97
+ // CommonJS (require('x'))
98
+ if (
99
+ node.type === "ExpressionStatement" &&
100
+ node.expression.type === "CallExpression" &&
101
+ node.expression.callee.name === "require" &&
102
+ node.expression.arguments[0]?.type === "Literal"
103
+ ) {
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;
115
+ }
116
+ }
117
+ }
118
+
119
+ return visitedSet; // success
120
+ };
@@ -0,0 +1,107 @@
1
+ import { flattenedConfigKeyRegex } from "../constants/bases.js";
2
+
3
+ import { exitDueToFailure } from "../utilities/helpers.js";
4
+
5
+ /**
6
+ * 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.
11
+ */
12
+ export const flattenConfigData = (
13
+ configData,
14
+ configDataMap = new Map(),
15
+ parentKeys = []
16
+ ) => {
17
+ for (const [key, value] of Object.entries(configData)) {
18
+ const newKeys = [...parentKeys, key];
19
+ const normalizedKey = newKeys
20
+ .map((k) => k.toUpperCase())
21
+ .join("#")
22
+ .replace(/\s/g, "_");
23
+ const source = newKeys.join(" > ");
24
+
25
+ if (typeof value === "string") {
26
+ 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();
33
+ }
34
+
35
+ configDataMap.set(normalizedKey, {
36
+ value,
37
+ source,
38
+ });
39
+ } else if (typeof value === "object" && value && !Array.isArray(value)) {
40
+ /** @type {Record<string, any>} */
41
+ const typedValue = value;
42
+
43
+ flattenConfigData(typedValue, configDataMap, newKeys);
44
+ }
45
+ }
46
+
47
+ // At this point we're out of the recursion, and we can start working with the complete data.
48
+
49
+ // strips metadata
50
+ /**@type {Map<string, string>} */
51
+ const map = new Map();
52
+ configDataMap.forEach((value, key) => {
53
+ map.set(key, value.value);
54
+ });
55
+
56
+ // makes the flattened config data object
57
+ const flattenedConfigData = Object.fromEntries(map);
58
+
59
+ // The integrity of the flattened config data needs to be established before working with it safely.
60
+
61
+ const flattenedConfigDataKeysSet = new Set(Object.keys(flattenedConfigData));
62
+
63
+ const flattenedConfigDataValuesArray = Object.values(flattenedConfigData);
64
+ const flattenedConfigDataValuesSet = new Set(flattenedConfigDataValuesArray);
65
+
66
+ flattenedConfigDataKeysSet.forEach((key) => {
67
+ // checks the reversability of flattenedConfigData
68
+ 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();
73
+ }
74
+ if (!flattenedConfigKeyRegex.test(key)) {
75
+ // 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();
80
+ }
81
+ });
82
+
83
+ /** @type {Set<string>} */
84
+ const set = new Set();
85
+
86
+ flattenedConfigDataValuesArray.forEach((value) => {
87
+ if (set.has(value)) {
88
+ // 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();
93
+ }
94
+ set.add(value);
95
+ });
96
+
97
+ // Also including the reversed flattened config data.
98
+
99
+ const reversedFlattenedConfigData = Object.fromEntries(
100
+ Object.entries(flattenedConfigData).map(([key, value]) => [value, key])
101
+ );
102
+
103
+ return {
104
+ flattenedConfigData,
105
+ reversedFlattenedConfigData,
106
+ };
107
+ };