Skip to content
This repository has been archived by the owner on May 27, 2020. It is now read-only.

Commit

Permalink
feat: improve diagnostics
Browse files Browse the repository at this point in the history
  • Loading branch information
axetroy committed Feb 4, 2020
1 parent c9f51c1 commit a5f029e
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 91 deletions.
1 change: 1 addition & 0 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"typescript": "3.7.5",
"vscode-languageserver": "6.1.0",
"vscode-languageserver-textdocument": "1.0.0",
"vscode-uri": "^2.1.1",
"which": "2.0.2"
},
"devDependencies": {
Expand Down
42 changes: 34 additions & 8 deletions server/src/deno.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,13 @@ interface Version {
raw: string;
}

export type FormatableLanguages =
| "typescript"
interface DenoModule {
filepath: string;
raw: string;
remote: boolean;
}

export type FormatableLanguages = "typescript"
| "typescriptreact"
| "javascript"
| "javascriptreact"
Expand Down Expand Up @@ -93,9 +98,8 @@ class Deno {
[
"run",
"--allow-read",
`https://deno.land/std${
version ? "@v" + version : ""
}/prettier/main.ts`,
`https://deno.land/std${version ? "@v" + version : ""
}/prettier/main.ts`,
"--stdin",
"--stdin-parser",
parser,
Expand Down Expand Up @@ -170,6 +174,29 @@ class Deno {

return deps;
}
public resolveModule(cwd: string, moduleName: string): DenoModule {
let remote = false;
const raw = moduleName;
if (/^https?:\/\/.+/.test(moduleName)) {
remote = true;
moduleName = path.resolve(
this.DENO_DEPS_DIR,
moduleName.replace("://", "/")
);
} // absolute filepath
else if (moduleName.indexOf("/") === 0) {
moduleName = moduleName;
} // relative filepath
else {
moduleName = path.resolve(cwd, moduleName);
}

return {
filepath: moduleName,
raw,
remote
};
}
private getDenoDir(): string {
let denoDir = process.env["DENO_DIR"];

Expand All @@ -194,9 +221,8 @@ class Deno {
return denoDir;
}
private async getExecutablePath(): Promise<string | undefined> {
const denoPath = await which("deno").catch(() =>
Promise.resolve(undefined)
);
const denoPath = await which("deno")
.catch(() => Promise.resolve(undefined));

return denoPath;
}
Expand Down
113 changes: 71 additions & 42 deletions server/src/diagnostics.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import * as path from "path";

import {
IConnection,
Range,
Expand All @@ -6,8 +8,17 @@ import {
} from "vscode-languageserver";
import { TextDocument } from "vscode-languageserver-textdocument";
import * as ts from "typescript";
import { URI } from "vscode-uri";

import { Bridge } from "./bridge";
import { deno } from "./deno";

enum ErrorCode {
InvalidModule = 1001,
MissingExtension = 1002,
PreferHTTPS = 1003,
ModuleNotExist = 1004
}

export class Diagnostics {
constructor(
Expand Down Expand Up @@ -43,7 +54,7 @@ export class Diagnostics {
ts.ScriptKind.TSX
);
} catch (err) {
return;
return [];
}

const moduleNodes: ts.LiteralLikeNode[] = [];
Expand Down Expand Up @@ -116,69 +127,87 @@ export class Diagnostics {
".wasm": true
};

const invalidImportModulesDiagnostics: Diagnostic[] = moduleNodes
.map(moduleNode => {
const numberOfSpaces = Math.abs(
// why plus 2?
// because `moduleNode.text` only contain the plaintext without two quotes
moduleNode.end - moduleNode.pos - (moduleNode.text.length + 2)
);
const dir = path.dirname(URI.parse(document.uri).path);
const diagnosticsForThisDocument = [];

const range = Range.create(
document.positionAt(moduleNode.pos + numberOfSpaces),
document.positionAt(moduleNode.end)
);
for (const moduleNode of moduleNodes) {
const numberOfSpaces = Math.abs(
// why plus 2?
// because `moduleNode.text` only contain the plaintext without two quotes
moduleNode.end - moduleNode.pos - (moduleNode.text.length + 2)
);

if (
/^\..+/.test(moduleNode.text) === false &&
/^https?:\/\/.*/.test(moduleNode.text) === false
) {
return Diagnostic.create(
const range = Range.create(
document.positionAt(moduleNode.pos + numberOfSpaces),
document.positionAt(moduleNode.end)
);

const isRelativeModule = /^\..+/.test(moduleNode.text);
const isRemoteModule = /^https?:\/\/.*/.test(moduleNode.text);

if (!isRelativeModule && !isRemoteModule) {
diagnosticsForThisDocument.push(
Diagnostic.create(
range,
`Deno only supports importting \`relative/HTTP\` module.`,
DiagnosticSeverity.Error,
1001,
ErrorCode.InvalidModule,
this.name
);
}
)
);
}

{
const [extensionName] = moduleNode.text.match(/\.[a-zA-Z\d]+$/) ||
[];
{
const [extensionName] = moduleNode.text.match(/\.[a-zA-Z\d]+$/) || [];

if (!validExtensionNameMap[extensionName]) {
return Diagnostic.create(
if (!validExtensionNameMap[extensionName]) {
diagnosticsForThisDocument.push(
Diagnostic.create(
range,
`Please specify valid extension name of the imported module.`,
DiagnosticSeverity.Error,
1002,
ErrorCode.MissingExtension,
this.name
);
}
)
);
}
}

if (/^https?:\/\//.test(moduleNode.text)) {
if (/^https:\/\//.test(moduleNode.text) === false) {
const range = Range.create(
document.positionAt(moduleNode.pos),
document.positionAt(moduleNode.end)
);
if (isRemoteModule) {
if (/^https:\/\//.test(moduleNode.text) === false) {
const range = Range.create(
document.positionAt(moduleNode.pos),
document.positionAt(moduleNode.end)
);

return Diagnostic.create(
diagnosticsForThisDocument.push(
Diagnostic.create(
range,
`For security, we recommend using the HTTPS module.`,
DiagnosticSeverity.Warning,
1003,
ErrorCode.PreferHTTPS,
this.name
);
}
)
);
}
}

const module = deno.resolveModule(dir, moduleNode.text);

return;
})
.filter(v => v);
if (!ts.sys.fileExists(module.filepath)) {
diagnosticsForThisDocument.push(
Diagnostic.create(
range,
`Cannot found module \`${module.raw}\`.`,
DiagnosticSeverity.Error,
ErrorCode.ModuleNotExist,
this.name
)
);
}
}

return invalidImportModulesDiagnostics;
return diagnosticsForThisDocument;
}
async diagnosis(document: TextDocument): Promise<void> {
this.connection.sendDiagnostics({
Expand Down
6 changes: 3 additions & 3 deletions server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ const connection: IConnection = createConnection(
new IPCMessageWriter(process)
);

const bridge = new Bridge(connection);
const diagnostics = new Diagnostics(SERVER_NAME, connection, bridge);

// Create a simple text document manager. The text document manager
// supports full document sync only
const documents = new TextDocuments(TextDocument);

const bridge = new Bridge(connection);
const diagnostics = new Diagnostics(SERVER_NAME, connection, bridge);

connection.onInitialize(
(params): InitializeResult => {
return {
Expand Down
69 changes: 31 additions & 38 deletions typescript-deno-plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,29 +62,37 @@ module.exports = function init({
};

return {
create(info: ts_module.server.PluginCreateInfo): ts_module.LanguageService {
create(info:
ts_module.server.PluginCreateInfo): ts_module.LanguageService
{
logger = Logger.forPlugin(info);

logger.info(`Create typescript-deno-plugin`);
const getCompilationSettings = info.languageServiceHost.getCompilationSettings.bind(
info.languageServiceHost
);
const getScriptFileNames = info.languageServiceHost.getScriptFileNames.bind(
info.languageServiceHost
);
const getCompilationSettings = info.languageServiceHost
.getCompilationSettings.bind(
info.languageServiceHost
);
const getScriptFileNames = info.languageServiceHost.getScriptFileNames
.bind(
info.languageServiceHost
);
// ref https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API#customizing-module-resolution
const resolveModuleNames = info.languageServiceHost.resolveModuleNames?.bind(
info.languageServiceHost
);
const getCompletionEntryDetails = info.languageService.getCompletionEntryDetails.bind(
info.languageService
);
const getCompletionsAtPosition = info.languageService.getCompletionsAtPosition.bind(
info.languageService
);
const getSemanticDiagnostics = info.languageService.getSemanticDiagnostics.bind(
info.languageService
);
const resolveModuleNames = info.languageServiceHost.resolveModuleNames
?.bind(
info.languageServiceHost
);
const getCompletionEntryDetails = info.languageService
.getCompletionEntryDetails.bind(
info.languageService
);
const getCompletionsAtPosition = info.languageService
.getCompletionsAtPosition.bind(
info.languageService
);
const getSemanticDiagnostics = info.languageService
.getSemanticDiagnostics.bind(
info.languageService
);

info.languageServiceHost.getCompilationSettings = () => {
const projectConfig = getCompilationSettings();
Expand Down Expand Up @@ -145,8 +153,7 @@ module.exports = function init({
fileName: string,
position: number,
name: string,
formatOptions?:
| ts_module.FormatCodeOptions
formatOptions?: ts_module.FormatCodeOptions
| ts_module.FormatCodeSettings,
source?: string,
preferences?: ts_module.UserPreferences
Expand Down Expand Up @@ -191,25 +198,12 @@ module.exports = function init({
return diagnostics;
}

const ignoreCodeMapInDeno: { [k: number]: boolean } = {
const ignoreCodeMapInDeno: { [k: number]: boolean; } = {
// 2691:true, // can not import module which end with `.ts`
1308: true // support top level await 只允许在异步函数中使用 "await" 表达式
};

return diagnostics.filter(v => {
if (v.code === 2691) {
v.code = 2307; // ts error code which can not found module
const reg = /“[^”]+”/g;
const message =
typeof v.messageText === "string"
? v.messageText
: v.messageText.messageText;

const matcher = message.match(reg);
const [, moduleName] = matcher || [];

v.messageText = `Cannot find module ${moduleName}`;
}
return !ignoreCodeMapInDeno[v.code];
});
};
Expand Down Expand Up @@ -262,9 +256,8 @@ module.exports = function init({
function getModuleWithQueryString(moduleName: string): string | undefined {
let name = moduleName;
for (
const index = name.indexOf("?");
index !== -1;
name = name.substring(index + 1)
const index = name.indexOf("?"); index !== -1; name = name
.substring(index + 1)
) {
if (name.substring(0, index).endsWith(".ts")) {
const cutLength = moduleName.length - name.length;
Expand Down

0 comments on commit a5f029e

Please sign in to comment.