1 | "use strict";
|
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
|
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
|
4 | };
|
5 | Object.defineProperty(exports, "__esModule", { value: true });
|
6 | exports.cli = exports.resolveConfig = void 0;
|
7 | const cli_color_1 = __importDefault(require("cli-color"));
|
8 | const commander_1 = __importDefault(require("commander"));
|
9 | const fs_1 = __importDefault(require("fs"));
|
10 | const google_closure_deps_1 = require("google-closure-deps");
|
11 | const lodash_difference_1 = __importDefault(require("lodash.difference"));
|
12 | const path_1 = __importDefault(require("path"));
|
13 | const util_1 = require("util");
|
14 | const clilogger_1 = __importDefault(require("./clilogger"));
|
15 | const fix_1 = require("./fix");
|
16 | const parser_1 = require("./parser");
|
17 |
|
18 |
|
19 | const { version } = require("../package.json");
|
20 | function list(val) {
|
21 | return val.split(",");
|
22 | }
|
23 | function map(val) {
|
24 | const mapping = new Map();
|
25 | val.split(",").forEach((item) => {
|
26 | const [key, value] = item.split(":");
|
27 | mapping.set(key, value);
|
28 | });
|
29 | return mapping;
|
30 | }
|
31 | function setCommandOptions(command) {
|
32 | return command
|
33 | .version(version, "-v, --version")
|
34 | .usage("[options] files...")
|
35 | .option("-f, --fix-in-place", "Fix the file in-place.")
|
36 | .option("--provideRoots <roots>", "Root namespaces to provide separated by comma.", list)
|
37 | .option("--ignoreProvides", "Provides will remain unchanged")
|
38 | .option("--namespaces <methods>", "Provided namespaces separated by comma.", list)
|
39 | .option("--replaceMap <map>", 'Methods or properties to namespaces mapping like "before1:after1,before2:after2".', map)
|
40 | .option("--useForwardDeclare", "Use goog.forwardDeclare() instead of goog.requireType().")
|
41 | .option("--config <file>", ".fixclosurerc file path.")
|
42 | .option("--depsJs <files>", "deps.js file paths separated by comma.", list)
|
43 | .option("--showSuccess", "Show success ouput.")
|
44 | .option("--no-color", "Disable color highlight.");
|
45 | }
|
46 | function getDuplicated(namespaces) {
|
47 | const dups = new Set();
|
48 | namespaces.reduce((prev, cur) => {
|
49 | if (prev === cur) {
|
50 | dups.add(cur);
|
51 | }
|
52 | return cur;
|
53 | }, null);
|
54 | return [...dups];
|
55 | }
|
56 |
|
57 |
|
58 |
|
59 | function findConfig(opt_dir) {
|
60 | return findConfig_(opt_dir || process.cwd());
|
61 | }
|
62 | const findConfig_ = memoize((dir) => {
|
63 | const filename = ".fixclosurerc";
|
64 | const filepath = path_1.default.normalize(path_1.default.join(dir, filename));
|
65 | try {
|
66 | fs_1.default.accessSync(filepath);
|
67 | return filepath;
|
68 | }
|
69 | catch {
|
70 |
|
71 | }
|
72 | const parent = path_1.default.resolve(dir, "../");
|
73 | if (dir === parent) {
|
74 | return null;
|
75 | }
|
76 | return findConfig_(parent);
|
77 | });
|
78 | function parseArgs(argv, opt_dir) {
|
79 | const program = new commander_1.default.Command();
|
80 | const opts = setCommandOptions(program).parse(argv).opts();
|
81 | if (Array.isArray(opts.depsJs) && opts.depsJs.length > 0) {
|
82 | const results = opts.depsJs.map((file) => google_closure_deps_1.parser.parseFile(path_1.default.resolve(opt_dir || process.cwd(), file)));
|
83 | const symbols = results
|
84 | .map((result) => result.dependencies.map((dep) => dep.closureSymbols))
|
85 | .flat(2);
|
86 | opts.depsJsSymbols = symbols;
|
87 | }
|
88 | return { opts, program };
|
89 | }
|
90 | function resolveConfig({ config, cwd } = {}) {
|
91 | const configPath = config || findConfig(cwd);
|
92 | if (!configPath) {
|
93 | return null;
|
94 | }
|
95 | const opts = fs_1.default.readFileSync(configPath, "utf8").trim().split(/\s+/);
|
96 | const argv = ["node", "fixclosure", ...opts];
|
97 | return parseArgs(argv, path_1.default.dirname(configPath));
|
98 | }
|
99 | exports.resolveConfig = resolveConfig;
|
100 | async function getFiles(args) {
|
101 | const { globby } = await import("globby");
|
102 | return globby(args, {
|
103 | expandDirectories: { files: ["*"], extensions: ["js"] },
|
104 | });
|
105 | }
|
106 | async function main(argv, stdout, stderr, exit) {
|
107 | const { opts: argsOptions, program } = parseArgs(argv);
|
108 | const { opts: rcOptions } = resolveConfig({ config: argsOptions.config }) ?? {};
|
109 | const options = { ...rcOptions, ...argsOptions };
|
110 | if (program.args.length < 1) {
|
111 | program.outputHelp();
|
112 | exit(1);
|
113 | }
|
114 |
|
115 | options.providedNamespace = (options.depsJsSymbols || []).concat(options.namespaces || []);
|
116 | let ok = 0;
|
117 | let ng = 0;
|
118 | let fixed = 0;
|
119 | const files = await getFiles(program.args);
|
120 | const promises = files.map(async (file) => {
|
121 | const log = new clilogger_1.default(options.color, stdout, stderr);
|
122 | log.warn(`File: ${file}\n`);
|
123 | const src = await (0, util_1.promisify)(fs_1.default.readFile)(file, "utf8");
|
124 | const parser = new parser_1.Parser(options);
|
125 | const info = parser.parse(src);
|
126 | if (options.useForwardDeclare) {
|
127 | info.toForwardDeclare = info.toRequireType;
|
128 | info.toRequireType = [];
|
129 | }
|
130 | log.info("Provided:");
|
131 | log.items(info.provided.map((item) => item + (info.ignoredProvide.includes(item) ? " (ignored)" : "")));
|
132 | log.info("");
|
133 | log.info("Required:");
|
134 | log.items(info.required.map((item) => item + (info.ignoredRequire.includes(item) ? " (ignored)" : "")));
|
135 | log.info("");
|
136 | if (info.requireTyped.length > 0) {
|
137 | log.info("RequireTyped:");
|
138 | log.items(info.requireTyped.map((item) => item + (info.ignoredRequireType.includes(item) ? " (ignored)" : "")));
|
139 | log.info("");
|
140 | }
|
141 | if (info.forwardDeclared.length > 0) {
|
142 | log.info("ForwardDeclared:");
|
143 | log.items(info.forwardDeclared.map((item) => item +
|
144 | (info.ignoredForwardDeclare.includes(item) ? " (ignored)" : "")));
|
145 | log.info("");
|
146 | }
|
147 | let needToFix = false;
|
148 | needToFix =
|
149 | checkDeclare(log, "Provide", info.provided, info.toProvide, info.ignoredProvide) || needToFix;
|
150 | needToFix =
|
151 | checkDeclare(log, "Require", info.required, info.toRequire, info.ignoredRequire) || needToFix;
|
152 | needToFix =
|
153 | checkDeclare(log, "RequireType", info.requireTyped, info.toRequireType, info.ignoredRequireType, info.forwardDeclared, info.toForwardDeclare) || needToFix;
|
154 | needToFix =
|
155 | checkDeclare(log, "ForwardDeclare", info.forwardDeclared, info.toForwardDeclare, info.ignoredForwardDeclare, info.requireTyped, info.toRequireType) || needToFix;
|
156 | if (needToFix) {
|
157 | if (options.fixInPlace) {
|
158 | await (0, fix_1.fixInPlace)(file, src, info);
|
159 | log.raw("FIXED!", cli_color_1.default.cyan);
|
160 | fixed++;
|
161 | }
|
162 | else {
|
163 | log.error("FAIL!");
|
164 | ng++;
|
165 | }
|
166 | log.flush(false);
|
167 | }
|
168 | else {
|
169 | ok++;
|
170 | log.success("GREEN!");
|
171 | if (options.showSuccess) {
|
172 | log.flush(true);
|
173 | }
|
174 | }
|
175 | });
|
176 | let hasException = false;
|
177 | try {
|
178 | await Promise.all(promises);
|
179 | }
|
180 | catch (e) {
|
181 | console.error(e);
|
182 | hasException = true;
|
183 | }
|
184 | const log = new clilogger_1.default(options.color, stdout, stderr);
|
185 | const total = files.length;
|
186 | log.info("");
|
187 | log.info(`Total: ${total} files`);
|
188 | log.success(`Passed: ${ok} files`);
|
189 | if (ng) {
|
190 | log.error(`Failed: ${ng} files`);
|
191 | }
|
192 | if (fixed) {
|
193 | log.warn(`Fixed: ${fixed} files`);
|
194 | }
|
195 | if (ng || hasException) {
|
196 | log.flush(false);
|
197 | exit(1);
|
198 | }
|
199 | else {
|
200 | log.flush(true);
|
201 | }
|
202 | }
|
203 | exports.cli = main;
|
204 | function checkDeclare(log, method, declared, toDeclare, ignoredDeclare, optionalDeclared = [], optionalToDeclare = []) {
|
205 | let needToFix = false;
|
206 | const duplicated = getDuplicated(declared);
|
207 | if (duplicated.length > 0) {
|
208 | needToFix = true;
|
209 | log.error(`Duplicated ${method}:`);
|
210 | log.items(duplicated);
|
211 | log.info("");
|
212 | }
|
213 | const missing = (0, lodash_difference_1.default)(toDeclare, declared, optionalDeclared);
|
214 | if (missing.length > 0) {
|
215 | needToFix = true;
|
216 | log.error(`Missing ${method}:`);
|
217 | log.items(missing);
|
218 | log.info("");
|
219 | }
|
220 | let unnecessary = (0, lodash_difference_1.default)(declared, toDeclare, ignoredDeclare, optionalToDeclare);
|
221 | unnecessary = uniqArray(unnecessary);
|
222 | if (unnecessary.length > 0) {
|
223 | needToFix = true;
|
224 | log.error(`Unnecessary ${method}:`);
|
225 | log.items(unnecessary);
|
226 | log.info("");
|
227 | }
|
228 | return needToFix;
|
229 | }
|
230 | function uniqArray(array) {
|
231 | return [...new Set(array)];
|
232 | }
|
233 | function memoize(func) {
|
234 | const cache = new Map();
|
235 | return (key, ...args) => {
|
236 | if (!cache.has(key)) {
|
237 | cache.set(key, func(key, ...args));
|
238 | }
|
239 |
|
240 | return cache.get(key);
|
241 | };
|
242 | }
|