UNPKG

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