comment-variables 0.0.5 → 0.2.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.
- package/comments.config.js +17 -8
- package/library/_commons/constants/bases.js +77 -0
- package/library/_commons/rules/compress.js +78 -0
- package/library/_commons/rules/resolve.js +77 -0
- package/library/_commons/utilities/flows.js +102 -0
- package/library/_commons/utilities/helpers.js +15 -0
- package/{run-with-config.js → library/_commons/utilities/run-with-config.js} +56 -17
- package/library/index.js +144 -0
- package/package.json +14 -8
- package/comments.config.ts +0 -24
- package/import.js +0 -5
- package/import2.js +0 -1
- package/index.js +0 -343
- package/test-file.js +0 -7
- /package/{find-all-imports.js → library/_commons/utilities/find-all-imports.js} +0 -0
package/comments.config.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { test } from "./import.js";
|
|
2
|
-
|
|
3
1
|
const config = {
|
|
2
|
+
// for testing
|
|
4
3
|
levelOne: {
|
|
5
4
|
levelTwo: {
|
|
6
5
|
levelThree: "Level three.",
|
|
@@ -11,14 +10,24 @@ 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
|
-
|
|
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: {
|
|
22
|
+
exitDueToFailure: "Terminates the whole process with a 'failure' code (1).",
|
|
23
|
+
},
|
|
20
24
|
};
|
|
21
25
|
|
|
22
|
-
export default config;
|
|
26
|
+
// export default config;
|
|
27
|
+
|
|
28
|
+
const trueConfig = {
|
|
29
|
+
data: config,
|
|
30
|
+
ignores: ["chocolat.js"],
|
|
31
|
+
};
|
|
23
32
|
|
|
24
|
-
|
|
33
|
+
export default trueConfig;
|
|
@@ -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,78 @@
|
|
|
1
|
+
import { placeholderMessageId } from "../constants/bases.js";
|
|
2
|
+
|
|
3
|
+
import { escapeRegex } from "..//utilities/helpers.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
* @param {{[key: string]: string}} reversedFlattenedConfig
|
|
8
|
+
* @returns
|
|
9
|
+
*/
|
|
10
|
+
const makeRule = (reversedFlattenedConfig) => {
|
|
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 sortedReversedFlattenedConfig = Object.entries(
|
|
13
|
+
reversedFlattenedConfig
|
|
14
|
+
).sort(([a], [b]) => b.length - a.length);
|
|
15
|
+
|
|
16
|
+
/** @type {import('@typescript-eslint/utils').TSESLint.RuleModule<string, []>} */
|
|
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 sortedReversedFlattenedConfig) {
|
|
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
|
+
*
|
|
8
|
+
* @param {{[key: string]: string}} flattenedConfig
|
|
9
|
+
* @returns
|
|
10
|
+
*/
|
|
11
|
+
const makeRule = (flattenedConfig) => {
|
|
12
|
+
/** @type {import('@typescript-eslint/utils').TSESLint.RuleModule<string, []>} */
|
|
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 = flattenedConfig[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,102 @@
|
|
|
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
|
+
|
|
14
|
+
import makeResolveRule from "../rules/resolve.js";
|
|
15
|
+
import makeCompressRule from "../rules/compress.js";
|
|
16
|
+
|
|
17
|
+
const coreCommentsFlow = async (
|
|
18
|
+
ruleName,
|
|
19
|
+
ignores,
|
|
20
|
+
makeCommentsRule,
|
|
21
|
+
flattenedConfigData
|
|
22
|
+
) => {
|
|
23
|
+
const eslint = new ESLint({
|
|
24
|
+
fix: true,
|
|
25
|
+
errorOnUnmatchedPattern: false,
|
|
26
|
+
overrideConfigFile: true,
|
|
27
|
+
overrideConfig: [
|
|
28
|
+
{
|
|
29
|
+
files: allJSTSFileGlobs,
|
|
30
|
+
ignores,
|
|
31
|
+
languageOptions: typeScriptAndJSXCompatible,
|
|
32
|
+
plugins: {
|
|
33
|
+
[commentVariablesPluginName]: {
|
|
34
|
+
rules: {
|
|
35
|
+
[ruleName]: makeCommentsRule(flattenedConfigData),
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
rules: {
|
|
40
|
+
[`${commentVariablesPluginName}/${ruleName}`]: "warn", // doesn't block builds, just applies fix
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
files: allMDFileGlobs,
|
|
45
|
+
ignores,
|
|
46
|
+
plugins: { markdown },
|
|
47
|
+
processor: "markdown/markdown",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
files: allMDVirtualJSTSFileGlobs,
|
|
51
|
+
ignores,
|
|
52
|
+
languageOptions: typeScriptAndJSXCompatible,
|
|
53
|
+
rules: {
|
|
54
|
+
[`${commentVariablesPluginName}/${ruleName}`]: "warn",
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const results = await eslint.lintFiles([
|
|
61
|
+
...allJSTSFileGlobs,
|
|
62
|
+
...allMDFileGlobs,
|
|
63
|
+
]);
|
|
64
|
+
await ESLint.outputFixes(results);
|
|
65
|
+
|
|
66
|
+
console.log({ results });
|
|
67
|
+
|
|
68
|
+
const resolvedOrCompressed =
|
|
69
|
+
ruleName === resolveRuleName
|
|
70
|
+
? "Resolved"
|
|
71
|
+
: ruleName === compressRuleName
|
|
72
|
+
? "Compressed"
|
|
73
|
+
: "Unknown rule name'd";
|
|
74
|
+
|
|
75
|
+
const total = results.reduce((sum, r) => {
|
|
76
|
+
const add = r.output ? 1 : 0;
|
|
77
|
+
return sum + add;
|
|
78
|
+
}, 0);
|
|
79
|
+
|
|
80
|
+
console.log(
|
|
81
|
+
`✅ ${resolvedOrCompressed} ${total} comment${total === 1 ? "" : "s"}.`
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const resolveCommentsFlow = async (ignores, flattenedConfigData) =>
|
|
86
|
+
coreCommentsFlow(
|
|
87
|
+
resolveRuleName,
|
|
88
|
+
ignores,
|
|
89
|
+
makeResolveRule,
|
|
90
|
+
flattenedConfigData
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
export const compressCommentsFlow = async (
|
|
94
|
+
ignores,
|
|
95
|
+
reversedFlattenedConfigData
|
|
96
|
+
) =>
|
|
97
|
+
coreCommentsFlow(
|
|
98
|
+
compressRuleName,
|
|
99
|
+
ignores,
|
|
100
|
+
makeCompressRule,
|
|
101
|
+
reversedFlattenedConfigData
|
|
102
|
+
);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* exitDueToFailure */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Terminates the whole process with a 'failure' code (1).
|
|
5
|
+
* @returns Never.
|
|
6
|
+
*/
|
|
7
|
+
export const exitDueToFailure = () => process.exit(1);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Escapes all regex characters with a `"\"` in a string to prepare it for use in a regex
|
|
11
|
+
* @param {string} string The string.
|
|
12
|
+
* @returns The string with regex characters escaped.
|
|
13
|
+
*/
|
|
14
|
+
export const escapeRegex = (string) =>
|
|
15
|
+
string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -3,14 +3,16 @@ import { pathToFileURL } from "url";
|
|
|
3
3
|
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
import { configKeyRegex } from "../constants/bases.js";
|
|
7
|
+
|
|
8
|
+
function flattenConfigData(
|
|
9
|
+
configData,
|
|
8
10
|
normalizedPath = "",
|
|
9
11
|
map = {},
|
|
10
12
|
pathStack = [],
|
|
11
|
-
|
|
13
|
+
reversedFlattenedConfigData = {}
|
|
12
14
|
) {
|
|
13
|
-
for (const [key, value] of Object.entries(
|
|
15
|
+
for (const [key, value] of Object.entries(configData)) {
|
|
14
16
|
const currentPath = [...pathStack, key];
|
|
15
17
|
normalizedPath = currentPath
|
|
16
18
|
.map((k) => k.toUpperCase())
|
|
@@ -31,18 +33,18 @@ function flattenConfig(
|
|
|
31
33
|
__source: currentPath.join(" > "), // for debugging
|
|
32
34
|
};
|
|
33
35
|
} else if (typeof value === "object") {
|
|
34
|
-
|
|
36
|
+
flattenConfigData(value, normalizedPath, map, currentPath);
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
const
|
|
40
|
+
const flattenedConfigData = Object.fromEntries(
|
|
39
41
|
Object.entries(map).map(([k, v]) => [k, v.value])
|
|
40
42
|
); // strip metadata
|
|
41
43
|
|
|
42
44
|
const set = new Set();
|
|
43
45
|
|
|
44
46
|
// the integrity of the config needs to be established before working with it
|
|
45
|
-
for (const value of Object.values(
|
|
47
|
+
for (const value of Object.values(flattenedConfigData)) {
|
|
46
48
|
if (set.has(value)) {
|
|
47
49
|
// checks that no two values are duplicate
|
|
48
50
|
throw new Error(
|
|
@@ -52,11 +54,14 @@ function flattenConfig(
|
|
|
52
54
|
set.add(value);
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
for (const [key, value] of Object.entries(
|
|
56
|
-
|
|
57
|
+
for (const [key, value] of Object.entries(flattenedConfigData)) {
|
|
58
|
+
reversedFlattenedConfigData[value] = key;
|
|
57
59
|
}
|
|
58
60
|
|
|
59
|
-
return {
|
|
61
|
+
return {
|
|
62
|
+
flattenedConfigData,
|
|
63
|
+
reversedFlattenedConfigData,
|
|
64
|
+
};
|
|
60
65
|
}
|
|
61
66
|
|
|
62
67
|
export async function runWithConfig(configPath) {
|
|
@@ -71,11 +76,15 @@ export async function runWithConfig(configPath) {
|
|
|
71
76
|
const config = configModule.default;
|
|
72
77
|
|
|
73
78
|
// Step 3: Validate config object
|
|
74
|
-
if (!config || typeof config !== "object") {
|
|
75
|
-
console.warn(
|
|
79
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
80
|
+
console.warn(
|
|
81
|
+
"Invalid config format. The config should be an object. Exiting."
|
|
82
|
+
);
|
|
76
83
|
return null;
|
|
77
84
|
}
|
|
78
85
|
|
|
86
|
+
// data
|
|
87
|
+
|
|
79
88
|
const RecursiveObject = z
|
|
80
89
|
.lazy(() =>
|
|
81
90
|
z.record(
|
|
@@ -121,18 +130,48 @@ export async function runWithConfig(configPath) {
|
|
|
121
130
|
path: [key],
|
|
122
131
|
});
|
|
123
132
|
}
|
|
133
|
+
if (!configKeyRegex.test(key)) {
|
|
134
|
+
ctx.addIssue({
|
|
135
|
+
code: z.ZodIssueCode.custom,
|
|
136
|
+
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).`,
|
|
137
|
+
path: [key],
|
|
138
|
+
});
|
|
139
|
+
}
|
|
124
140
|
}
|
|
125
141
|
});
|
|
126
142
|
|
|
127
|
-
const
|
|
143
|
+
const recursiveObjectResult = RecursiveObject.safeParse(config.data);
|
|
144
|
+
|
|
145
|
+
if (!recursiveObjectResult.success) {
|
|
146
|
+
console.warn("Config data could not pass validation from zod.");
|
|
147
|
+
recursiveObjectResult.error.errors.map((e) => console.log(e.message));
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ignores
|
|
152
|
+
|
|
153
|
+
const StringArraySchema = z.array(
|
|
154
|
+
z.string({
|
|
155
|
+
message: `The config's "ignores" key array should be made of string or be empty.`,
|
|
156
|
+
}),
|
|
157
|
+
{
|
|
158
|
+
message: `The config's "ignores" key value should be an array of strings (or at the very least an empty array).`,
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const stringArraySchemaResult = StringArraySchema.safeParse(config.ignores);
|
|
128
163
|
|
|
129
|
-
if (!
|
|
130
|
-
console.warn("Config could not pass validation from zod.");
|
|
131
|
-
|
|
164
|
+
if (!stringArraySchemaResult.success) {
|
|
165
|
+
console.warn("Config ignores could not pass validation from zod.");
|
|
166
|
+
stringArraySchemaResult.error.errors.map((e) => console.log(e.message));
|
|
132
167
|
return null;
|
|
133
168
|
}
|
|
134
169
|
|
|
135
170
|
// Step 4: Do your thing
|
|
136
171
|
console.log("Running with config:", config);
|
|
137
|
-
return {
|
|
172
|
+
return {
|
|
173
|
+
...flattenConfigData(config.data), // finalized
|
|
174
|
+
configPath, // finalized
|
|
175
|
+
passedIgnores: config.ignores, // could be treated here eventually
|
|
176
|
+
};
|
|
138
177
|
}
|
package/library/index.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
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 { runWithConfig } from "./_commons/utilities/run-with-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 runWithConfig(rawConfigPath);
|
|
60
|
+
if (!results) exitDueToFailure();
|
|
61
|
+
|
|
62
|
+
const {
|
|
63
|
+
flattenedConfigData,
|
|
64
|
+
reversedFlattenedConfigData,
|
|
65
|
+
configPath,
|
|
66
|
+
passedIgnores,
|
|
67
|
+
} = results;
|
|
68
|
+
|
|
69
|
+
console.log("Flattened config is:", flattenedConfigData);
|
|
70
|
+
console.log("Reversed flattened config is:", reversedFlattenedConfigData);
|
|
71
|
+
console.log("Config path is:", configPath);
|
|
72
|
+
console.log("Passed ignores are:", passedIgnores);
|
|
73
|
+
|
|
74
|
+
// VALIDATES THE REVERSABILITY OF THE CONCEIVED flattenedConfigData
|
|
75
|
+
|
|
76
|
+
const keys = new Set([...Object.keys(flattenedConfigData)]);
|
|
77
|
+
const values = new Set([...Object.values(flattenedConfigData)]);
|
|
78
|
+
|
|
79
|
+
keys.forEach((key) => {
|
|
80
|
+
if (values.has(key)) {
|
|
81
|
+
console.error(
|
|
82
|
+
`The key "${key}" is and shouldn't be among the values of flattenedConfigData.`
|
|
83
|
+
);
|
|
84
|
+
exitDueToFailure();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// ADDRESSES THE --lint-config-imports FLAG, GIVEN THAT THE FILES IMPORTED BY THE CONFIG ARE IGNORED BY DEFAULT.
|
|
89
|
+
|
|
90
|
+
const lintConfigImports = commands.indexOf(lintConfigImportsFlag) >= 2;
|
|
91
|
+
const rawConfigPathIgnores = lintConfigImports
|
|
92
|
+
? [configPath]
|
|
93
|
+
: [...findAllImports(configPath)];
|
|
94
|
+
|
|
95
|
+
// the ignore paths must be relative
|
|
96
|
+
const configPathIgnores = rawConfigPathIgnores.map((e) =>
|
|
97
|
+
path.relative(cwd, e)
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
console.log(
|
|
101
|
+
lintConfigImports ? "Config path ignore is:" : "Config path ignores are:",
|
|
102
|
+
configPathIgnores
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// ADDRESSES THE --my-ignores-only FLAG, GIVEN THAT KNOWN IGNORES ARE IGNORED BY DEFAULT
|
|
106
|
+
|
|
107
|
+
const myIgnoresOnly = commands.indexOf(myIgnoresOnlyFlag) >= 2;
|
|
108
|
+
const rawIgnores = [...configPathIgnores, ...passedIgnores];
|
|
109
|
+
const ignores = myIgnoresOnly ? rawIgnores : [...rawIgnores, ...knownIgnores];
|
|
110
|
+
|
|
111
|
+
console.log("Ignores are:", ignores);
|
|
112
|
+
|
|
113
|
+
// ADDRESSES THE CORE COMMANDS "resolve" AND "compress".
|
|
114
|
+
|
|
115
|
+
const coreCommand = commands[2];
|
|
116
|
+
|
|
117
|
+
switch (coreCommand) {
|
|
118
|
+
case "resolve":
|
|
119
|
+
await resolveCommentsFlow(ignores, flattenedConfigData);
|
|
120
|
+
break;
|
|
121
|
+
case "compress":
|
|
122
|
+
await compressCommentsFlow(ignores, reversedFlattenedConfigData);
|
|
123
|
+
break;
|
|
124
|
+
case undefined: // falls through the default
|
|
125
|
+
default:
|
|
126
|
+
if (coreCommand && !coreCommand.startsWith("--"))
|
|
127
|
+
console.log(
|
|
128
|
+
`Core command not recognized. Choose between "resolve" and "compress".`
|
|
129
|
+
);
|
|
130
|
+
else
|
|
131
|
+
console.log(
|
|
132
|
+
`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.${
|
|
133
|
+
passedConfigPath || lintConfigImports || myIgnoresOnly
|
|
134
|
+
? " (And DON'T FORGET YOUR FLAGS!)"
|
|
135
|
+
: ""
|
|
136
|
+
}`
|
|
137
|
+
);
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* Notes
|
|
142
|
+
I'm going to have to redo this, but for now I just want to vibe code it in order to see how it is possible to make this.
|
|
143
|
+
Edit: Code vibing, not vibe coding. That's what I did here.
|
|
144
|
+
*/
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "comment-variables",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "0.2.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,13 +14,19 @@
|
|
|
14
14
|
"scripts": {
|
|
15
15
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
16
16
|
},
|
|
17
|
-
"keywords": [
|
|
18
|
-
|
|
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": {
|
|
27
|
+
"@eslint/markdown": "^6.5.0",
|
|
22
28
|
"eslint": "^9.29.0",
|
|
23
|
-
"get-sourcecode-from-file-path": "^1.0.
|
|
29
|
+
"get-sourcecode-from-file-path": "^1.0.1",
|
|
24
30
|
"resolve-importing-path": "^1.0.2",
|
|
25
31
|
"tsconfig-paths": "^4.2.0",
|
|
26
32
|
"typescript-eslint": "^8.34.1",
|
package/comments.config.ts
DELETED
|
@@ -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
|
-
*/
|
package/import.js
DELETED
package/import2.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const test2 = "test2";
|
package/index.js
DELETED
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// The hashbang (#!) 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
|
-
import fs from "fs";
|
|
6
|
-
|
|
7
|
-
import { ESLint } from "eslint";
|
|
8
|
-
import tseslint from "typescript-eslint";
|
|
9
|
-
|
|
10
|
-
import { runWithConfig } from "./run-with-config.js";
|
|
11
|
-
import { findAllImports } from "./find-all-imports.js";
|
|
12
|
-
|
|
13
|
-
const cwd = process.cwd();
|
|
14
|
-
|
|
15
|
-
// ENSURES THE CLI TOOL ONLY RUN IN FOLDERS THAT POSSESS A package.json FILE AND A .git FOLDER.
|
|
16
|
-
|
|
17
|
-
const hasPackageJson = fs.existsSync(path.join(cwd, "package.json"));
|
|
18
|
-
if (!hasPackageJson) {
|
|
19
|
-
console.error(
|
|
20
|
-
"ERROR. No package.json file found in this directory. Aborting to prevent accidental changes."
|
|
21
|
-
);
|
|
22
|
-
process.exit(1);
|
|
23
|
-
}
|
|
24
|
-
const hasGitFolder = fs.existsSync(path.join(cwd, ".git"));
|
|
25
|
-
if (!hasGitFolder) {
|
|
26
|
-
console.error(
|
|
27
|
-
"ERROR. No git folder found in this directory. Aborting to prevent irreversible changes."
|
|
28
|
-
);
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// GATHERS COMMANDS.
|
|
33
|
-
|
|
34
|
-
const commands = process.argv;
|
|
35
|
-
|
|
36
|
-
// OBTAINS THE VALIDATED FLATTENED CONFIG, REVERSE FLATTENED CONFIG, AND CONFIG PATH.
|
|
37
|
-
|
|
38
|
-
const configFlagIndex = commands.indexOf("--config");
|
|
39
|
-
const passedConfigPath =
|
|
40
|
-
configFlagIndex >= 2 ? path.join(cwd, commands[configFlagIndex + 1]) : null;
|
|
41
|
-
const rawConfigPath = passedConfigPath ?? path.join(cwd, "comments.config.js");
|
|
42
|
-
|
|
43
|
-
const results = await runWithConfig(rawConfigPath);
|
|
44
|
-
if (!results) process.exit(1);
|
|
45
|
-
|
|
46
|
-
const { flattenedConfig, reversedFlattenedConfig, configPath } = results;
|
|
47
|
-
console.log("Config path is:", configPath);
|
|
48
|
-
console.log("Verified flattened config is:", flattenedConfig);
|
|
49
|
-
console.log("Reversed flattened config is:", reversedFlattenedConfig);
|
|
50
|
-
|
|
51
|
-
const keys = new Set([...Object.keys(flattenedConfig)]);
|
|
52
|
-
const values = new Set([...Object.values(flattenedConfig)]);
|
|
53
|
-
|
|
54
|
-
// VALIDATES ONE LAST TIME THE REVERSABILITY OF flattenedConfig AND reversedFlattenedConfig.
|
|
55
|
-
|
|
56
|
-
keys.forEach((key) => {
|
|
57
|
-
if (values.has(key)) {
|
|
58
|
-
console.error(
|
|
59
|
-
`The key "${key}" is and shouldn't be among the values of flattenedConfig.`
|
|
60
|
-
);
|
|
61
|
-
process.exit(1);
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
// ADDRESSES THE --include-config-imports FLAG, GIVEN THAT THE FILES IMPORTED BY THE CONFIG ARE IGNORED BY DEFAULT.
|
|
66
|
-
|
|
67
|
-
const includeConfigImports = commands.indexOf("--include-config-imports") >= 2;
|
|
68
|
-
const rawConfigIgnores = includeConfigImports
|
|
69
|
-
? [configPath]
|
|
70
|
-
: [...findAllImports(configPath)];
|
|
71
|
-
|
|
72
|
-
// the ignore paths must be relative
|
|
73
|
-
const configIgnores = rawConfigIgnores.map((e) => path.relative(cwd, e));
|
|
74
|
-
console.log(
|
|
75
|
-
includeConfigImports ? "Config ignore is:" : "Config ignores are",
|
|
76
|
-
configIgnores
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
// DEFINES DEFAULT ESLINT IGNORES AND FILES.
|
|
80
|
-
|
|
81
|
-
const knownIgnores = [
|
|
82
|
-
".next",
|
|
83
|
-
".react-router",
|
|
84
|
-
"node_modules",
|
|
85
|
-
".parcel-cache",
|
|
86
|
-
".react-router-parcel",
|
|
87
|
-
"dist",
|
|
88
|
-
];
|
|
89
|
-
|
|
90
|
-
const allJSTSFileGlobs = [
|
|
91
|
-
"**/*.tsx",
|
|
92
|
-
"**/*.ts",
|
|
93
|
-
"**/*.jsx",
|
|
94
|
-
"**/*.js",
|
|
95
|
-
"**/*.mjs",
|
|
96
|
-
"**/*.cjs",
|
|
97
|
-
];
|
|
98
|
-
|
|
99
|
-
// MAKES THE FLOW FOR resolveCommentsInProject.
|
|
100
|
-
|
|
101
|
-
const typeScriptAndJSXCompatible = {
|
|
102
|
-
// for compatibility with TypeScript (.ts and .tsx)
|
|
103
|
-
parser: tseslint.parser,
|
|
104
|
-
// for compatibility with JSX (React, etc.)
|
|
105
|
-
parserOptions: {
|
|
106
|
-
ecmaFeatures: {
|
|
107
|
-
jsx: true,
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
/** @type {import('@typescript-eslint/utils').TSESLint.RuleModule<string, []>} */
|
|
113
|
-
const jsCommentsRule = {
|
|
114
|
-
meta: {
|
|
115
|
-
type: "suggestion",
|
|
116
|
-
docs: {
|
|
117
|
-
description: "Resolve $COMMENT#... using js-comments config.",
|
|
118
|
-
},
|
|
119
|
-
messages: {
|
|
120
|
-
message: `Resolved $COMMENT placeholder(s) in comment.`,
|
|
121
|
-
},
|
|
122
|
-
fixable: "code",
|
|
123
|
-
schema: [],
|
|
124
|
-
},
|
|
125
|
-
create: (context) => {
|
|
126
|
-
const sourceCode = context.sourceCode;
|
|
127
|
-
const comments = sourceCode
|
|
128
|
-
.getAllComments()
|
|
129
|
-
.filter((e) => e.type !== "Shebang");
|
|
130
|
-
|
|
131
|
-
for (const comment of comments) {
|
|
132
|
-
const matches = [...comment.value.matchAll(/\$COMMENT#([A-Z0-9#_]+)/g)];
|
|
133
|
-
|
|
134
|
-
if (matches.length === 0) continue;
|
|
135
|
-
|
|
136
|
-
let fixedText = comment.value;
|
|
137
|
-
let hasValidFix = false;
|
|
138
|
-
|
|
139
|
-
for (const match of matches) {
|
|
140
|
-
const fullMatch = match[0]; // e.g. $COMMENT#LEVELONE#LEVELTWO
|
|
141
|
-
const key = match[1]; // e.g. LEVELONE#LEVELTWO
|
|
142
|
-
const replacement = flattenedConfig[key];
|
|
143
|
-
|
|
144
|
-
if (replacement) {
|
|
145
|
-
fixedText = fixedText.replace(fullMatch, replacement);
|
|
146
|
-
hasValidFix = true;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (hasValidFix && fixedText !== comment.value) {
|
|
151
|
-
context.report({
|
|
152
|
-
loc: comment.loc,
|
|
153
|
-
messageId: "message",
|
|
154
|
-
fix(fixer) {
|
|
155
|
-
const range = comment.range;
|
|
156
|
-
const prefix = comment.type === "Block" ? "/*" : "//";
|
|
157
|
-
const suffix = comment.type === "Block" ? "*/" : "";
|
|
158
|
-
const newComment = `${prefix}${fixedText}${suffix}`;
|
|
159
|
-
|
|
160
|
-
return fixer.replaceTextRange(range, newComment);
|
|
161
|
-
},
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return {};
|
|
167
|
-
},
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
async function resolveCommentsInProject(fileGlobs = allJSTSFileGlobs) {
|
|
171
|
-
const ruleName = "js-comments/js-comments-autofix";
|
|
172
|
-
|
|
173
|
-
const eslint = new ESLint({
|
|
174
|
-
fix: true,
|
|
175
|
-
errorOnUnmatchedPattern: false,
|
|
176
|
-
overrideConfigFile: true,
|
|
177
|
-
overrideConfig: [
|
|
178
|
-
{
|
|
179
|
-
files: fileGlobs,
|
|
180
|
-
ignores: [...configIgnores, ...knownIgnores], // 🚫 Ensure config isn't linted
|
|
181
|
-
languageOptions: typeScriptAndJSXCompatible,
|
|
182
|
-
plugins: {
|
|
183
|
-
"js-comments": {
|
|
184
|
-
rules: {
|
|
185
|
-
"js-comments-autofix": jsCommentsRule,
|
|
186
|
-
},
|
|
187
|
-
},
|
|
188
|
-
},
|
|
189
|
-
rules: {
|
|
190
|
-
[ruleName]: "warn", // Don't block builds, just apply fix
|
|
191
|
-
},
|
|
192
|
-
},
|
|
193
|
-
],
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
const results = await eslint.lintFiles(fileGlobs);
|
|
197
|
-
await ESLint.outputFixes(results);
|
|
198
|
-
|
|
199
|
-
console.log({ results });
|
|
200
|
-
|
|
201
|
-
const total = results.reduce((sum, r) => {
|
|
202
|
-
const add = r.output ? 1 : 0;
|
|
203
|
-
return sum + add;
|
|
204
|
-
}, 0);
|
|
205
|
-
console.log(`✅ Resolved ${total} comment${total === 1 ? "" : "s"}.`);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// MAKES THE FLOW FOR compressCommentsInProject.
|
|
209
|
-
|
|
210
|
-
const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
211
|
-
|
|
212
|
-
const makeReverseJsCommentsRule = (reversedFlattenedConfig) => {
|
|
213
|
-
// Sort the resolved values by descending length to prevent partial replacements.
|
|
214
|
-
const sortedReversedFlattenedConfig = Object.entries(
|
|
215
|
-
reversedFlattenedConfig
|
|
216
|
-
).sort(([a], [b]) => b.length - a.length);
|
|
217
|
-
|
|
218
|
-
/** @type {import('@typescript-eslint/utils').TSESLint.RuleModule<string, []>} */
|
|
219
|
-
const reverseJsCommentsRule = {
|
|
220
|
-
meta: {
|
|
221
|
-
type: "suggestion",
|
|
222
|
-
docs: {
|
|
223
|
-
description: "Resolve $COMMENT#... using js-comments config in reverse",
|
|
224
|
-
},
|
|
225
|
-
messages: {
|
|
226
|
-
message: `Comment compressed.`,
|
|
227
|
-
},
|
|
228
|
-
fixable: "code",
|
|
229
|
-
schema: [],
|
|
230
|
-
},
|
|
231
|
-
create(context) {
|
|
232
|
-
const sourceCode = context.sourceCode;
|
|
233
|
-
const comments = sourceCode
|
|
234
|
-
.getAllComments()
|
|
235
|
-
.filter((e) => e.type !== "Shebang");
|
|
236
|
-
|
|
237
|
-
for (const comment of comments) {
|
|
238
|
-
let fixedText = comment.value;
|
|
239
|
-
let modified = false;
|
|
240
|
-
|
|
241
|
-
for (const [
|
|
242
|
-
resolvedValue,
|
|
243
|
-
commentKey,
|
|
244
|
-
] of sortedReversedFlattenedConfig) {
|
|
245
|
-
const pattern = new RegExp(
|
|
246
|
-
`(?<=\\s|^)${escapeRegex(resolvedValue)}(?=\\s|$)`,
|
|
247
|
-
"g"
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
fixedText = fixedText.replace(pattern, () => {
|
|
251
|
-
modified = true;
|
|
252
|
-
return `$COMMENT#${commentKey}`;
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if (modified && fixedText !== comment.value) {
|
|
257
|
-
context.report({
|
|
258
|
-
loc: comment.loc,
|
|
259
|
-
messageId: "message",
|
|
260
|
-
fix(fixer) {
|
|
261
|
-
const fullCommentText =
|
|
262
|
-
comment.type === "Block"
|
|
263
|
-
? `/*${fixedText}*/`
|
|
264
|
-
: `//${fixedText}`;
|
|
265
|
-
return fixer.replaceText(comment, fullCommentText);
|
|
266
|
-
},
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
return {};
|
|
272
|
-
},
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
return reverseJsCommentsRule;
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
async function compressCommentsInProject(fileGlobs = allJSTSFileGlobs) {
|
|
279
|
-
const ruleName = "js-comments/js-comments-autofix";
|
|
280
|
-
|
|
281
|
-
const eslint = new ESLint({
|
|
282
|
-
fix: true,
|
|
283
|
-
errorOnUnmatchedPattern: false,
|
|
284
|
-
overrideConfigFile: true,
|
|
285
|
-
overrideConfig: [
|
|
286
|
-
{
|
|
287
|
-
files: fileGlobs,
|
|
288
|
-
ignores: [...configIgnores, ...knownIgnores], // 🚫 Ensure config isn't linted
|
|
289
|
-
languageOptions: typeScriptAndJSXCompatible,
|
|
290
|
-
plugins: {
|
|
291
|
-
"js-comments": {
|
|
292
|
-
rules: {
|
|
293
|
-
"js-comments-autofix": makeReverseJsCommentsRule(
|
|
294
|
-
reversedFlattenedConfig
|
|
295
|
-
),
|
|
296
|
-
},
|
|
297
|
-
},
|
|
298
|
-
},
|
|
299
|
-
rules: {
|
|
300
|
-
[ruleName]: "warn", // Don't block builds, just apply fix
|
|
301
|
-
},
|
|
302
|
-
},
|
|
303
|
-
],
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
const results = await eslint.lintFiles(fileGlobs);
|
|
307
|
-
await ESLint.outputFixes(results);
|
|
308
|
-
|
|
309
|
-
console.log({ results });
|
|
310
|
-
|
|
311
|
-
const total = results.reduce((sum, r) => {
|
|
312
|
-
const add = r.output ? 1 : 0;
|
|
313
|
-
return sum + add;
|
|
314
|
-
}, 0);
|
|
315
|
-
console.log(`✅ Compressed ${total} comment${total === 1 ? "" : "s"}.`);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// ADDRESSES THE CORE COMMANDS "resolve" AND "compress".
|
|
319
|
-
|
|
320
|
-
const coreCommand = commands[2];
|
|
321
|
-
|
|
322
|
-
switch (coreCommand) {
|
|
323
|
-
case "resolve":
|
|
324
|
-
await resolveCommentsInProject();
|
|
325
|
-
break;
|
|
326
|
-
case "compress":
|
|
327
|
-
await compressCommentsInProject();
|
|
328
|
-
break;
|
|
329
|
-
case undefined:
|
|
330
|
-
console.log(
|
|
331
|
-
`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.`
|
|
332
|
-
);
|
|
333
|
-
break;
|
|
334
|
-
default:
|
|
335
|
-
console.log(
|
|
336
|
-
`Core command not recognized. Choose between "resolve" and "compress".`
|
|
337
|
-
);
|
|
338
|
-
break;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/* Notes
|
|
342
|
-
I'm going to have to redo this, but for now I just want to vibe code it in order to see how it is possible to make this.
|
|
343
|
-
*/
|
package/test-file.js
DELETED
|
File without changes
|