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.
@@ -0,0 +1,110 @@
1
+ import { ESLint } from "eslint";
2
+ import markdown from "@eslint/markdown";
3
+
4
+ import {
5
+ commentVariablesPluginName,
6
+ resolveRuleName,
7
+ compressRuleName,
8
+ allJSTSFileGlobs,
9
+ allMDFileGlobs,
10
+ allMDVirtualJSTSFileGlobs,
11
+ typeScriptAndJSXCompatible,
12
+ } from "../constants/bases.js";
13
+ import { ruleNames_makeRules } from "../constants/rules.js";
14
+
15
+ /* coreCommentsFlow */
16
+
17
+ /**
18
+ * The core flow at the heart of resolving and compressing comments.
19
+ * @param {typeof resolveRuleName | typeof compressRuleName} ruleName The name of the rule currently used. (Either `"resolve"` or `"compress"`.)
20
+ * @param {string[]} ignores The array of paths and globs for the flow's ESLint instance to ignore.
21
+ * @param {{[key: string]: string}} flattenedConfigData Either the flattened config data or the reversed flattened config data, since they share the same structure.
22
+ */
23
+ const coreCommentsFlow = async (ruleName, ignores, flattenedConfigData) => {
24
+ const eslint = new ESLint({
25
+ fix: true,
26
+ errorOnUnmatchedPattern: false,
27
+ overrideConfigFile: true,
28
+ overrideConfig: [
29
+ {
30
+ files: allJSTSFileGlobs,
31
+ ignores,
32
+ languageOptions: typeScriptAndJSXCompatible,
33
+ plugins: {
34
+ [commentVariablesPluginName]: {
35
+ rules: {
36
+ [ruleName]: ruleNames_makeRules[ruleName](flattenedConfigData),
37
+ },
38
+ },
39
+ },
40
+ rules: {
41
+ [`${commentVariablesPluginName}/${ruleName}`]: "warn",
42
+ },
43
+ },
44
+ {
45
+ files: allMDFileGlobs,
46
+ ignores,
47
+ plugins: { markdown },
48
+ processor: "markdown/markdown",
49
+ },
50
+ {
51
+ files: allMDVirtualJSTSFileGlobs,
52
+ ignores,
53
+ languageOptions: typeScriptAndJSXCompatible,
54
+ rules: {
55
+ [`${commentVariablesPluginName}/${ruleName}`]: "warn",
56
+ },
57
+ },
58
+ ],
59
+ });
60
+
61
+ const results = await eslint.lintFiles([
62
+ ...allJSTSFileGlobs,
63
+ ...allMDFileGlobs,
64
+ ]);
65
+ await ESLint.outputFixes(results);
66
+
67
+ console.log({ results });
68
+
69
+ const resolvedOrCompressed =
70
+ ruleName === resolveRuleName
71
+ ? "Resolved"
72
+ : ruleName === compressRuleName
73
+ ? "Compressed"
74
+ : "Unknown rule name'd";
75
+
76
+ const total = results.reduce((sum, r) => {
77
+ const add = r.output ? 1 : 0;
78
+ return sum + add;
79
+ }, 0);
80
+
81
+ console.log(
82
+ `✅ ${resolvedOrCompressed} comments on ${total} file${
83
+ total === 1 ? "" : "s"
84
+ }.`
85
+ );
86
+ };
87
+
88
+ /* resolveCommentsFlow */
89
+
90
+ /**
91
+ * The flow that resolves $COMMENT#* placeholders intro actual comments.
92
+ * @param {string[]} ignores The array of paths and globs for the flow's ESLint instance to ignore.
93
+ * @param {{[key: string]: string}} flattenedConfigData The flattened config data, with $COMMENT#* placeholders as keys and actual comments as values.
94
+ * @returns
95
+ */
96
+ export const resolveCommentsFlow = async (ignores, flattenedConfigData) =>
97
+ coreCommentsFlow(resolveRuleName, ignores, flattenedConfigData);
98
+
99
+ /* compressCommentsFlow */
100
+
101
+ /**
102
+ * The flow that compresses actual comments into $COMMENT#* placeholders.
103
+ * @param {string[]} ignores The array of paths and globs for the flow's ESLint instance to ignore.
104
+ * @param {{[key: string]: string}} reversedFlattenedConfigData The reversed flattened config data, with actual comments as keys and $COMMENT#* placeholders as values.
105
+ * @returns
106
+ */
107
+ export const compressCommentsFlow = async (
108
+ ignores,
109
+ reversedFlattenedConfigData
110
+ ) => coreCommentsFlow(compressRuleName, ignores, reversedFlattenedConfigData);
@@ -0,0 +1,17 @@
1
+ /* exitDueToFailure */
2
+
3
+ /**
4
+ * Terminates the whole process with a 'failure' code (1).
5
+ * @returns {never} Never. (Somehow typing needs to be explicit for unreachable code inference.)
6
+ */
7
+ export const exitDueToFailure = () => process.exit(1);
8
+
9
+ /* escapeRegex */
10
+
11
+ /**
12
+ * Escapes all regex characters with a `"\"` in a string to prepare it for use in a regex.
13
+ * @param {string} string The string.
14
+ * @returns The string with regex characters escaped.
15
+ */
16
+ export const escapeRegex = (string) =>
17
+ string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -0,0 +1,67 @@
1
+ import { existsSync } from "fs";
2
+ import { pathToFileURL } from "url";
3
+
4
+ import { flattenConfigData } from "./flatten-config-data.js";
5
+
6
+ import { ConfigDataSchema, ConfigIgnoresSchema } from "../schemas/config.js";
7
+
8
+ /**
9
+ * 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 and the raw passed ignores.
12
+ */
13
+ export async function resolveConfig(configPath) {
14
+ // Step 1: Checks if config file exists
15
+
16
+ if (!existsSync(configPath)) {
17
+ console.warn("No config file found. Exiting gracefully.");
18
+ return null;
19
+ }
20
+
21
+ // Step 2: Imports the config dynamically
22
+
23
+ const configModule = await import(pathToFileURL(configPath));
24
+ const config = configModule.default;
25
+
26
+ // Step 3: Validates config object
27
+
28
+ // validates config
29
+ 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;
34
+ }
35
+
36
+ // validates config.data
37
+ const configDataResult = ConfigDataSchema.safeParse(config.data);
38
+
39
+ 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;
43
+ }
44
+
45
+ // validates config.ignores
46
+ const configIgnoresSchemaResult = ConfigIgnoresSchema.safeParse(
47
+ config.ignores
48
+ );
49
+
50
+ 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;
54
+ }
55
+
56
+ // sends back:
57
+ // - the flattened config data,
58
+ // - the reverse flattened config data,
59
+ // - the verified config path
60
+ // - and the raw passed ignores
61
+ console.log("Running with config:", config);
62
+ return {
63
+ ...flattenConfigData(configDataResult.data), // finalized
64
+ configPath, // finalized
65
+ passedIgnores: configIgnoresSchemaResult.data, // addressed with --lint-config-imports and --my-ignores-only to be finalized
66
+ };
67
+ }
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+ // The shebang (#!) is necessary to communicate with Unix-based systems, like Linux and macOS. On Windows, it is ignored, but npm tooling bridges the gap by generating wrappers that make the CLI work anyway.
3
+
4
+ import path from "path";
5
+
6
+ import {
7
+ cwd,
8
+ hasPackageJson,
9
+ hasGitFolder,
10
+ defaultConfigFileName,
11
+ configFlag,
12
+ lintConfigImportsFlag,
13
+ myIgnoresOnlyFlag,
14
+ knownIgnores,
15
+ } from "./_commons/constants/bases.js";
16
+
17
+ import { exitDueToFailure } from "./_commons/utilities/helpers.js";
18
+ import { resolveConfig } from "./_commons/utilities/resolve-config.js";
19
+ import { findAllImports } from "./_commons/utilities/find-all-imports.js";
20
+
21
+ import {
22
+ resolveCommentsFlow,
23
+ compressCommentsFlow,
24
+ } from "./_commons/utilities/flows.js";
25
+
26
+ // ENSURES THE CLI TOOL ONLY RUN IN FOLDERS THAT POSSESS A package.json FILE AND A .git FOLDER.
27
+
28
+ if (!hasPackageJson) {
29
+ console.error(
30
+ "ERROR. No package.json file found in this directory. Aborting to prevent accidental changes."
31
+ );
32
+ exitDueToFailure();
33
+ }
34
+ if (!hasGitFolder) {
35
+ console.error(
36
+ "ERROR. No git folder found in this directory. Aborting to prevent irreversible changes."
37
+ );
38
+ exitDueToFailure();
39
+ }
40
+
41
+ // GATHERS COMMANDS.
42
+
43
+ const commands = process.argv;
44
+
45
+ // OBTAINS THE VALIDATED FLATTENED CONFIG, REVERSE FLATTENED CONFIG, AND CONFIG PATH.
46
+
47
+ // extracts the position of the --config flag
48
+ const configFlagIndex = commands.indexOf(configFlag);
49
+ // determines if there's a valid config flag input
50
+ const hasConfigFlag = configFlagIndex >= 2;
51
+ // determines if there's an actual config path passed to the config flag
52
+ const passedConfig = commands[configFlagIndex + 1];
53
+ // gets the absolute passed config path if the --config flag is set
54
+ const passedConfigPath =
55
+ hasConfigFlag && passedConfig ? path.join(cwd, passedConfig) : null;
56
+ // defaults to comments.config.js if no --config flag is set
57
+ const rawConfigPath = passedConfigPath ?? path.join(cwd, defaultConfigFileName);
58
+
59
+ const results = await resolveConfig(rawConfigPath);
60
+ if (!results) {
61
+ exitDueToFailure();
62
+ }
63
+
64
+ const {
65
+ flattenedConfigData,
66
+ reversedFlattenedConfigData,
67
+ configPath,
68
+ passedIgnores,
69
+ } = results;
70
+
71
+ console.log("Flattened config is:", flattenedConfigData);
72
+ console.log("Reversed flattened config is:", reversedFlattenedConfigData);
73
+ console.log("Config path is:", configPath);
74
+ console.log("Passed ignores are:", passedIgnores);
75
+
76
+ // ADDRESSES THE --lint-config-imports FLAG, GIVEN THAT THE FILES IMPORTED BY THE CONFIG ARE IGNORED BY DEFAULT.
77
+
78
+ const lintConfigImports = commands.indexOf(lintConfigImportsFlag) >= 2;
79
+ const rawConfigPathIgnores = lintConfigImports
80
+ ? [configPath]
81
+ : [...(findAllImports(configPath) ?? [])];
82
+
83
+ // the ignore paths must be relative
84
+ const configPathIgnores = rawConfigPathIgnores.map((e) =>
85
+ path.relative(cwd, e)
86
+ );
87
+
88
+ console.log(
89
+ lintConfigImports ? "Config path ignore is:" : "Config path ignores are:",
90
+ configPathIgnores
91
+ );
92
+
93
+ // ADDRESSES THE --my-ignores-only FLAG, GIVEN THAT KNOWN IGNORES ARE IGNORED BY DEFAULT
94
+
95
+ const myIgnoresOnly = commands.indexOf(myIgnoresOnlyFlag) >= 2;
96
+ const rawIgnores = [...configPathIgnores, ...passedIgnores];
97
+ const ignores = myIgnoresOnly ? rawIgnores : [...rawIgnores, ...knownIgnores];
98
+
99
+ console.log("Ignores are:", ignores);
100
+
101
+ // ADDRESSES THE CORE COMMANDS "resolve" AND "compress".
102
+
103
+ const coreCommand = commands[2];
104
+
105
+ switch (coreCommand) {
106
+ case "resolve":
107
+ await resolveCommentsFlow(ignores, flattenedConfigData);
108
+ break;
109
+ case "compress":
110
+ await compressCommentsFlow(ignores, reversedFlattenedConfigData);
111
+ break;
112
+ case undefined: // falls through the default
113
+ default:
114
+ if (coreCommand && !coreCommand.startsWith("--"))
115
+ console.error(
116
+ `ERROR. Core command not recognized. Choose between "resolve" and "compress".`
117
+ );
118
+ else
119
+ console.log(
120
+ `If these settings are correct with you, feel free to initiate the command "resolve" to resolve comments, or "compress" to compress them back to their $COMMENT#* forms.${
121
+ passedConfigPath || lintConfigImports || myIgnoresOnly
122
+ ? " (And DON'T FORGET YOUR FLAGS!)"
123
+ : ""
124
+ }`
125
+ );
126
+ break;
127
+ }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "comment-variables",
3
- "version": "0.1.0",
4
- "description": "",
3
+ "version": "0.3.0",
4
+ "description": "A CLI tool for configuring, managing and maintaining JavaScript comments as JavaScript variables.",
5
5
  "bin": {
6
- "jscomments": "./index.js",
7
- "comment-variables": "./index.js"
6
+ "jscomments": "./library/index.js",
7
+ "comment-variables": "./library/index.js"
8
8
  },
9
- "main": "index.js",
9
+ "main": "library/index.js",
10
10
  "repository": {
11
11
  "type": "git",
12
12
  "url": "git+https://github.com/LutherTS/jscomments.git"
@@ -14,14 +14,19 @@
14
14
  "scripts": {
15
15
  "test": "echo \"Error: no test specified\" && exit 1"
16
16
  },
17
- "keywords": [],
18
- "author": "",
17
+ "keywords": [
18
+ "cli",
19
+ "javascript",
20
+ "comments",
21
+ "variables"
22
+ ],
23
+ "author": "Luther Tchofo Safo <luther@tchofo-safo-portfolio.me>",
19
24
  "license": "MIT",
20
25
  "type": "module",
21
26
  "dependencies": {
22
27
  "@eslint/markdown": "^6.5.0",
23
28
  "eslint": "^9.29.0",
24
- "get-sourcecode-from-file-path": "^1.0.0",
29
+ "get-sourcecode-from-file-path": "^1.0.1",
25
30
  "resolve-importing-path": "^1.0.2",
26
31
  "tsconfig-paths": "^4.2.0",
27
32
  "typescript-eslint": "^8.34.1",
@@ -1,24 +0,0 @@
1
- import { test } from "./import.js";
2
-
3
- const config = {
4
- levelOne: {
5
- levelTwo: {
6
- levelThree: "Level three.",
7
- // levelthree: "Also level three.", // errors
8
- // alsoLevelThree: "Level three.", // errors
9
- levelThreeBis: "Level three bis.",
10
- levelThreeTer: "Level three ter.",
11
- levelThreeAlso: "Also level three here.",
12
- levelThreeToo: "This too is level three.",
13
- // test: "LEVELONE#LEVELTWO#LEVELTHREE", // errors
14
- },
15
- },
16
- };
17
-
18
- export default config;
19
-
20
- // This too is level three.
21
-
22
- /* Notes
23
- I'll need to install TypeScript to test this.
24
- */
@@ -1,102 +0,0 @@
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
- * Helper to process and recursively resolve a single import path.
11
- * Returns false if resolution fails at any level.
12
- */
13
- const processImport = (
14
- importPath,
15
- currentDir,
16
- cwd,
17
- visited,
18
- depth,
19
- maxDepth
20
- ) => {
21
- const resolvedPath = resolveImportingPath(currentDir, importPath, cwd);
22
- if (!resolvedPath) return true; // Skips unresolved paths (not an error).
23
-
24
- const result = findAllImports(
25
- resolvedPath,
26
- cwd,
27
- visited,
28
- depth + 1,
29
- maxDepth
30
- );
31
- return result !== null; // Returns false if child failed.
32
- };
33
-
34
- export const findAllImports = (
35
- filePath,
36
- cwd = process.cwd(),
37
- visited = new Set(),
38
- depth = 0,
39
- maxDepth = 100
40
- ) => {
41
- // Early failure checks (with logging).
42
- if (depth > maxDepth) {
43
- console.error(`ERROR. Max depth ${maxDepth} reached at ${filePath}.`);
44
- return null;
45
- }
46
- if (!fs.existsSync(filePath)) {
47
- console.error(`ERROR. File not found at ${filePath}.`);
48
- return null;
49
- }
50
- if (visited.has(filePath)) return visited;
51
-
52
- // Parses AST.
53
- visited.add(filePath);
54
- const sourceCode = getSourceCodeFromFilePath(filePath);
55
- if (!sourceCode?.ast) {
56
- console.error(`ERROR. Failed to parse AST for ${filePath}.`);
57
- return null;
58
- }
59
-
60
- // Processes all imports.
61
- const currentDir = path.dirname(filePath);
62
- for (const node of sourceCode.ast.body) {
63
- // ES Modules (import x from 'y')
64
- if (node.type === "ImportDeclaration") {
65
- if (
66
- !processImport(
67
- node.source.value,
68
- currentDir,
69
- cwd,
70
- visited,
71
- depth,
72
- maxDepth
73
- )
74
- ) {
75
- return null;
76
- }
77
- }
78
-
79
- // CommonJS (require('x'))
80
- if (
81
- node.type === "ExpressionStatement" &&
82
- node.expression.type === "CallExpression" &&
83
- node.expression.callee.name === "require" &&
84
- node.expression.arguments[0]?.type === "Literal"
85
- ) {
86
- if (
87
- !processImport(
88
- node.expression.arguments[0].value,
89
- currentDir,
90
- cwd,
91
- visited,
92
- depth,
93
- maxDepth
94
- )
95
- ) {
96
- return null;
97
- }
98
- }
99
- }
100
-
101
- return visited; // success
102
- };
package/import.js DELETED
@@ -1,5 +0,0 @@
1
- import { test2 } from "./import2.js";
2
-
3
- export const test = test2;
4
-
5
- // This too is level three.
package/import2.js DELETED
@@ -1 +0,0 @@
1
- export const test2 = "test2";