UNPKG

17.8 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 });
6// modified from https://github.com/Microsoft/typescript-tslint-plugin
7const path_1 = __importDefault(require("path"));
8const fs_1 = __importDefault(require("fs"));
9const url_1 = require("url");
10const merge_deep_1 = __importDefault(require("merge-deep"));
11const import_maps_1 = require("import-maps");
12const logger_1 = require("./logger");
13const utils_1 = require("./utils");
14const universal_module_resolver_1 = require("./module_resolver/universal_module_resolver");
15const hash_meta_1 = require("./module_resolver/hash_meta");
16const codefix_provider_1 = require("./codefix_provider");
17require("./code_fixes");
18let logger;
19let pluginInfo;
20const config = {
21 enable: true,
22};
23let parsedImportMap;
24let projectDirectory;
25module.exports = function init({ typescript }) {
26 // see https://github.com/denoland/deno/blob/2debbdacb935cfe1eb7bb8d1f40a5063b339d90b/js/compiler.ts#L159-L170
27 const OPTIONS = {
28 allowJs: true,
29 checkJs: true,
30 esModuleInterop: true,
31 module: typescript.ModuleKind.ESNext,
32 moduleResolution: typescript.ModuleResolutionKind.NodeJs,
33 jsx: typescript.JsxEmit.React,
34 noEmit: true,
35 strict: true,
36 outDir: "$deno$",
37 removeComments: true,
38 stripComments: true,
39 resolveJsonModule: true,
40 sourceMap: true,
41 target: typescript.ScriptTarget.ESNext,
42 typeRoots: [],
43 };
44 const OPTIONS_OVERWRITE_BY_DENO = {
45 allowNonTsExtensions: false,
46 jsx: OPTIONS.jsx,
47 module: OPTIONS.module,
48 moduleResolution: OPTIONS.moduleResolution,
49 resolveJsonModule: OPTIONS.resolveJsonModule,
50 strict: OPTIONS.strict,
51 noEmit: OPTIONS.noEmit,
52 noEmitHelpers: OPTIONS.noEmitHelpers,
53 target: typescript.ScriptTarget.ESNext,
54 };
55 return {
56 create(info) {
57 logger = logger_1.Logger.forPlugin(info);
58 logger.info("plugin created.");
59 pluginInfo = info;
60 const tsLs = info.languageService;
61 const tsLsHost = info.languageServiceHost;
62 const project = info.project;
63 Object.assign(config, info.config);
64 if (!config.enable) {
65 logger.info("plugin disabled.");
66 return tsLs;
67 }
68 projectDirectory = project.getCurrentDirectory();
69 // TypeScript plugins have a `cwd` of `/`, which causes issues with import resolution.
70 process.chdir(projectDirectory);
71 const resolveTypeReferenceDirectives = tsLsHost.resolveTypeReferenceDirectives;
72 if (resolveTypeReferenceDirectives) {
73 tsLsHost.resolveTypeReferenceDirectives = (typeDirectiveNames, containingFile, redirectedReference, options) => {
74 const ret = resolveTypeReferenceDirectives.call(tsLsHost, typeDirectiveNames, containingFile, redirectedReference, options);
75 if (!config.enable) {
76 logger.info("plugin disabled.");
77 return ret;
78 }
79 return ret;
80 };
81 }
82 // ref https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API#customizing-module-resolution
83 const resolveModuleNames = tsLsHost.resolveModuleNames;
84 if (resolveModuleNames) {
85 tsLsHost.resolveModuleNames = (moduleNames, containingFile, ...rest) => {
86 logger.info("resolveModuleNames");
87 if (!config.enable) {
88 logger.info("plugin disabled.");
89 return resolveModuleNames.call(tsLsHost, moduleNames, containingFile, ...rest);
90 }
91 const resolvedModules = [];
92 parsedImportMap = parseImportMapFromFile(projectDirectory, config.importmap);
93 // try resolve typeReferenceDirectives
94 for (let moduleName of moduleNames) {
95 const parsedModuleName = parseModuleName(moduleName, containingFile, parsedImportMap, logger);
96 if (parsedModuleName == null) {
97 logger.info(`module "${moduleName}" can not parsed`);
98 resolvedModules.push(undefined);
99 continue;
100 }
101 const resolvedModule = resolveDenoModule(parsedModuleName);
102 if (!resolvedModule) {
103 logger.info(`module "${moduleName}" can not resolved`);
104 resolvedModules.push(undefined);
105 continue;
106 }
107 logger.info(`module "${moduleName}" -> ${resolvedModule.filepath}`);
108 resolvedModules.push({
109 extension: resolvedModule.extension,
110 isExternalLibraryImport: false,
111 resolvedFileName: resolvedModule.filepath,
112 });
113 const content = typescript.sys.readFile(resolvedModule.filepath);
114 if (!content) {
115 continue;
116 }
117 const { typeReferenceDirectives } = typescript.preProcessFile(content, true, true);
118 if (!typeReferenceDirectives.length) {
119 continue;
120 }
121 for (const typeRef of typeReferenceDirectives) {
122 const module = universal_module_resolver_1.universalModuleResolver.resolve(typeRef.fileName, containingFile);
123 if (module) {
124 resolvedModule.originModuleName = module.originModuleName;
125 resolvedModule.filepath = module.filepath;
126 }
127 }
128 }
129 return resolvedModules;
130 };
131 }
132 const getCompilationSettings = info.languageServiceHost.getCompilationSettings;
133 info.languageServiceHost.getCompilationSettings = () => {
134 if (!config.enable) {
135 return getCompilationSettings.call(tsLsHost);
136 }
137 const projectConfig = getCompilationSettings.call(info.languageServiceHost);
138 const compilationSettings = merge_deep_1.default(merge_deep_1.default(OPTIONS, projectConfig), OPTIONS_OVERWRITE_BY_DENO);
139 return compilationSettings;
140 };
141 const getScriptFileNames = info.languageServiceHost.getScriptFileNames;
142 info.languageServiceHost.getScriptFileNames = () => {
143 if (!config.enable) {
144 return getScriptFileNames.call(tsLsHost);
145 }
146 const originalScriptFileNames = getScriptFileNames.call(info.languageServiceHost);
147 const scriptFileNames = [...originalScriptFileNames];
148 const libDenoDts = utils_1.getDenoDtsPath("lib.deno.d.ts");
149 if (!libDenoDts) {
150 logger.info(`Can not load lib.deno.d.ts from ${libDenoDts}.`);
151 return scriptFileNames;
152 }
153 scriptFileNames.push(libDenoDts);
154 const libWebworkerDts = utils_1.getDenoDtsPath("lib.webworker.d.ts");
155 if (!libWebworkerDts) {
156 logger.info(`Can not load lib.webworker.d.ts from ${libWebworkerDts}.`);
157 return scriptFileNames;
158 }
159 scriptFileNames.push(libWebworkerDts);
160 return scriptFileNames;
161 };
162 function getCompletionEntryDetails(fileName, position, name, formatOptions, source, preferences) {
163 const details = tsLs.getCompletionEntryDetails(fileName, position, name, formatOptions, source, preferences);
164 if (!config.enable) {
165 return details;
166 }
167 if (details) {
168 if (details.codeActions && details.codeActions.length) {
169 for (const ca of details.codeActions) {
170 for (const change of ca.changes) {
171 if (!change.isNewFile) {
172 for (const tc of change.textChanges) {
173 tc.newText = tc.newText.replace(/^(import .* from ['"])(\..*)(['"];\n)/i, "$1$2.ts$3");
174 }
175 }
176 }
177 }
178 }
179 }
180 return details;
181 }
182 function getSemanticDiagnostics(filename) {
183 logger.info("getSemanticDiagnostics");
184 const diagnostics = tsLs.getSemanticDiagnostics(filename);
185 if (!config.enable) {
186 return diagnostics;
187 }
188 // ref: https://github.com/denoland/deno/blob/da8cb408c878aa6e90542e26173f1f14b5254d29/cli/js/compiler/util.ts#L262
189 const ignoredDiagnostics = [
190 // TS2306: File 'file:///Users/rld/src/deno/cli/tests/subdir/amd_like.js' is
191 // not a module.
192 2306,
193 // TS1375: 'await' expressions are only allowed at the top level of a file
194 // when that file is a module, but this file has no imports or exports.
195 // Consider adding an empty 'export {}' to make this file a module.
196 1375,
197 // TS1103: 'for-await-of' statement is only allowed within an async function
198 // or async generator.
199 1103,
200 // TS2691: An import path cannot end with a '.ts' extension. Consider
201 // importing 'bad-module' instead.
202 // !! 2691,
203 // TS5009: Cannot find the common subdirectory path for the input files.
204 5009,
205 // TS5055: Cannot write file
206 // 'http://localhost:4545/cli/tests/subdir/mt_application_x_javascript.j4.js'
207 // because it would overwrite input file.
208 5055,
209 // TypeScript is overly opinionated that only CommonJS modules kinds can
210 // support JSON imports. Allegedly this was fixed in
211 // Microsoft/TypeScript#26825 but that doesn't seem to be working here,
212 // so we will ignore complaints about this compiler setting.
213 5070,
214 // TS7016: Could not find a declaration file for module '...'. '...'
215 // implicitly has an 'any' type. This is due to `allowJs` being off by
216 // default but importing of a JavaScript module.
217 7016,
218 ];
219 return diagnostics.filter((d) => !ignoredDiagnostics.includes(d.code)).map((d) => {
220 var _a;
221 if (d.code === 2691) {
222 const moduleName = d.file.getFullText().substr(d.start + 1, d.length - 2);
223 if (config.importmap != null) {
224 parsedImportMap = parseImportMapFromFile(projectDirectory, config.importmap);
225 }
226 const parsedModuleName = parseModuleName(moduleName, (_a = d.file) === null || _a === void 0 ? void 0 : _a.fileName, parsedImportMap, logger);
227 if (parsedModuleName == null) {
228 d.code = 10001; // InvalidRelativeImport
229 d.messageText =
230 `relative import path "${moduleName}" not prefixed with / or ./ or ../`;
231 return d;
232 }
233 const resolvedModule = resolveDenoModule(parsedModuleName);
234 if (resolvedModule != null) {
235 return d;
236 }
237 if (utils_1.isHttpURL(parsedModuleName)) {
238 d.code = 10002; // RemoteModuleNotExist
239 if (moduleName === parsedModuleName) {
240 d.messageText =
241 `The remote module has not been cached locally. Try \`deno cache ${parsedModuleName}\` if it exists`;
242 }
243 else {
244 d.messageText =
245 `The remote module "${moduleName}" has not been cached locally. Try \`deno cache ${parsedModuleName}\` if it exists`;
246 }
247 return d;
248 }
249 if (path_1.default.isAbsolute(parsedModuleName) ||
250 parsedModuleName.startsWith("./") ||
251 parsedModuleName.startsWith("../") ||
252 parsedModuleName.startsWith("file://")) {
253 d.code = 10003; // LocalModuleNotExist
254 d.messageText = `Could not find module "${moduleName}" locally`;
255 return d;
256 }
257 d.code = 10004; // InvalidImport
258 d.messageText =
259 `Import module "${moduleName}" must be a relative path or remote HTTP URL`;
260 }
261 return d;
262 });
263 }
264 // TODO(justjavac): maybe also `getCombinedCodeFix`
265 function getCodeFixesAtPosition(fileName, start, end, errorCodes, formatOptions, preferences) {
266 const codeFixActions = tsLs.getCodeFixesAtPosition(fileName, start, end, errorCodes, formatOptions, preferences);
267 for (const errorCode of errorCodes) {
268 const fixes = codefix_provider_1.errorCodeToFixes.get(errorCode);
269 if (fixes == null)
270 continue;
271 for (const fix of fixes) {
272 fix.replaceCodeActions(codeFixActions);
273 }
274 }
275 return codeFixActions;
276 }
277 const proxy = Object.assign(Object.create(null), tsLs, {
278 getCompletionEntryDetails,
279 getSemanticDiagnostics,
280 getCodeFixesAtPosition,
281 });
282 return proxy;
283 },
284 onConfigurationChanged(c) {
285 var _a;
286 logger.info("config change to:\n" + JSON.stringify(c, null, " "));
287 Object.assign(config, c);
288 parsedImportMap = parseImportMapFromFile(projectDirectory, config.importmap);
289 pluginInfo.project.markAsDirty();
290 pluginInfo.project.refreshDiagnostics();
291 pluginInfo.project.updateGraph();
292 (_a = pluginInfo.languageService.getProgram()) === null || _a === void 0 ? void 0 : _a.emit();
293 },
294 };
295};
296function parseImportMapFromFile(cwd, file) {
297 const importmps = {
298 imports: {},
299 scopes: {},
300 };
301 if (file == null) {
302 return importmps;
303 }
304 if (!path_1.default.isAbsolute(file)) {
305 file = path_1.default.resolve(cwd, file);
306 }
307 const fullFilePath = utils_1.normalizeFilepath(file);
308 if (!utils_1.pathExistsSync(fullFilePath)) {
309 return importmps;
310 }
311 const content = fs_1.default.readFileSync(fullFilePath, {
312 encoding: "utf8",
313 });
314 try {
315 return import_maps_1.parseFromString(content, `file://${cwd}/`);
316 }
317 catch (_a) {
318 return importmps;
319 }
320}
321function parseModuleName(moduleName, containingFile, parsedImportMap, logger) {
322 if (parsedImportMap != null) {
323 try {
324 let scriptURL;
325 if (utils_1.isInDenoDir(containingFile)) {
326 const meta = hash_meta_1.HashMeta.create(`${containingFile}.metadata.json`);
327 if (meta && meta.url) {
328 scriptURL = meta.url;
329 }
330 else {
331 scriptURL = new url_1.URL("file:///" + path_1.default.dirname(containingFile) + "/");
332 }
333 }
334 else {
335 scriptURL = new url_1.URL("file:///" + path_1.default.dirname(containingFile) + "/");
336 }
337 logger && logger.info(`baseUrl: ${scriptURL}`);
338 const moduleUrl = import_maps_1.resolve(moduleName, parsedImportMap, scriptURL);
339 if (moduleUrl.protocol === "file:") {
340 return url_1.fileURLToPath(moduleUrl.href);
341 }
342 if (moduleUrl.protocol === "http:" || moduleUrl.protocol === "https:") {
343 return moduleUrl.href;
344 }
345 // just support protocol: file, http, https
346 return undefined;
347 }
348 catch (e) {
349 if (logger)
350 logger.info("moduleName: " + moduleName);
351 if (logger)
352 logger.info("e: " + e.stack);
353 return undefined;
354 }
355 }
356}
357function resolveDenoModule(moduleName) {
358 return universal_module_resolver_1.universalModuleResolver.resolve(moduleName);
359}
360//# sourceMappingURL=index.js.map
\No newline at end of file