UNPKG

9.11 kBJavaScriptView Raw
1"use strict";
2var __importDefault = (this && this.__importDefault) || function (mod) {
3 return (mod && mod.__esModule) ? mod : { "default": mod };
4};
5Object.defineProperty(exports, "__esModule", { value: true });
6exports.cli = exports.resolveConfig = void 0;
7const cli_color_1 = __importDefault(require("cli-color"));
8const commander_1 = __importDefault(require("commander"));
9const fs_1 = __importDefault(require("fs"));
10const google_closure_deps_1 = require("google-closure-deps");
11const lodash_difference_1 = __importDefault(require("lodash.difference"));
12const path_1 = __importDefault(require("path"));
13const util_1 = require("util");
14const clilogger_1 = __importDefault(require("./clilogger"));
15const fix_1 = require("./fix");
16const parser_1 = require("./parser");
17// Dont't use `import from` to avoid creating nested directory `./lib/src`.
18// eslint-disable-next-line @typescript-eslint/no-var-requires
19const { version } = require("../package.json");
20function list(val) {
21 return val.split(",");
22}
23function 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}
31function 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}
46function 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 * Find .fixclosurerc up from current working dir
58 */
59function findConfig(opt_dir) {
60 return findConfig_(opt_dir || process.cwd());
61}
62const 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 // ignore
71 }
72 const parent = path_1.default.resolve(dir, "../");
73 if (dir === parent) {
74 return null;
75 }
76 return findConfig_(parent);
77});
78function 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}
90function 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}
99exports.resolveConfig = resolveConfig;
100async function getFiles(args) {
101 const { globby } = await import("globby");
102 return globby(args, {
103 expandDirectories: { files: ["*"], extensions: ["js"] },
104 });
105}
106async 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 // for Parser
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}
203exports.cli = main;
204function 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}
230function uniqArray(array) {
231 return [...new Set(array)];
232}
233function 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 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
240 return cache.get(key);
241 };
242}