diff --git a/README.md b/README.md
index adfffb4..64e3810 100644
--- a/README.md
+++ b/README.md
@@ -84,10 +84,10 @@ The extension supports the following ways to load external declaration files
```ts
// @deno-types="./foo.d.ts"
-import * as foo from "./foo.js";
+import { foo } from "./foo.js";
```
-> This will not be implemented in then extensions.
+see [example](/examples/compile-hint/mod.ts)
2. `Triple-slash` reference directive
@@ -99,6 +99,8 @@ import { format } from "https://deno.land/x/date_fns/index.js";
format(new Date(), "yyyy/MM/DD");
```
+see [example](/examples/react/mod.tsx)
+
3. `X-TypeScript-Types` custom header
```ts
diff --git a/core/deno_deps.test.ts b/core/deno_deps.test.ts
index 7c47140..a8f8b62 100644
--- a/core/deno_deps.test.ts
+++ b/core/deno_deps.test.ts
@@ -42,9 +42,11 @@ import test from "test.ts";
import * as test from "test.ts";
export { window } from "export.ts";
export * from "export_as.ts";
-export * as xx from "export_as_default.ts";
+export * as xx from "export_as_default.ts"; // example
`,
- ts.ScriptTarget.ESNext
+ ts.ScriptTarget.ESNext,
+ true,
+ ts.ScriptKind.TSX
);
expect(getImportModules(ts)(sourceFile)).toStrictEqual([
@@ -57,6 +59,16 @@ export * as xx from "export_as_default.ts";
},
},
{
+ hint: undefined,
+ leadingComments: [
+ {
+ end: 97,
+ hasTrailingNewLine: true,
+ kind: 2,
+ pos: 0,
+ text: `/// `,
+ },
+ ],
moduleName: "./foo.ts",
location: {
start: { line: 1, character: 8 },
@@ -118,6 +130,15 @@ export * as xx from "export_as_default.ts";
start: { line: 9, character: 21 },
end: { line: 9, character: 41 },
},
+ trailingComments: [
+ {
+ end: 373,
+ hasTrailingNewLine: true,
+ kind: 2,
+ pos: 363,
+ text: "// example",
+ },
+ ],
},
] as ImportModule[]);
});
diff --git a/core/deno_deps.ts b/core/deno_deps.ts
index ef5c57f..6c49ab3 100644
--- a/core/deno_deps.ts
+++ b/core/deno_deps.ts
@@ -5,6 +5,11 @@ import typescript = require("typescript");
import { getDenoDepsDir } from "./deno";
import { HashMeta } from "./hash_meta";
+import { parseCompileHint } from "./deno_type_hint";
+
+interface Comment extends typescript.CommentRange {
+ text: string;
+}
export type Deps = {
url: string;
@@ -70,9 +75,18 @@ export async function getAllDenoCachedDeps(): Promise {
return deps;
}
+export type Hint = {
+ text: string;
+ range: Range;
+ contentRange: Range;
+};
+
export type ImportModule = {
moduleName: string;
+ hint?: Hint; // if import module with @deno-types="xxx" hint
location: Range;
+ leadingComments?: Comment[];
+ trailingComments?: Comment[];
};
export function getImportModules(ts: typeof typescript) {
@@ -145,15 +159,52 @@ export function getImportModules(ts: typeof typescript) {
// delint it
delint(sourceFile);
+ const text = sourceFile.getFullText();
+
+ const getComments = (
+ node: typescript.Node,
+ isTrailing: boolean
+ ): Comment[] | undefined => {
+ /* istanbul ignore else */
+ if (node.parent) {
+ const nodePos = isTrailing ? node.end : node.pos;
+ const parentPos = isTrailing ? node.parent.end : node.parent.pos;
+
+ if (
+ node.parent.kind === ts.SyntaxKind.SourceFile ||
+ nodePos !== parentPos
+ ) {
+ const comments = isTrailing
+ ? ts.getTrailingCommentRanges(sourceFile.text, nodePos)
+ : ts.getLeadingCommentRanges(sourceFile.text, nodePos);
+
+ if (Array.isArray(comments)) {
+ return comments.map((v) => {
+ const target: Comment = {
+ ...v,
+ text: text.substring(v.pos, v.end),
+ };
+
+ return target;
+ });
+ }
+
+ return undefined;
+ }
+ }
+ };
+
const modules: ImportModule[] = sourceFile.typeReferenceDirectives
.map((directive) => {
const start = sourceFile.getLineAndCharacterOfPosition(directive.pos);
const end = sourceFile.getLineAndCharacterOfPosition(directive.end);
- return {
+ const module: ImportModule = {
moduleName: directive.fileName,
location: { start, end },
};
+
+ return module;
})
.concat(
moduleNodes.map((node) => {
@@ -175,10 +226,30 @@ export function getImportModules(ts: typeof typescript) {
end,
};
- return {
+ const leadingComments = getComments(node.parent, false);
+ const trailingComments = getComments(node.parent, true);
+
+ const module: ImportModule = {
moduleName: node.text,
location,
};
+
+ if (trailingComments) {
+ module.trailingComments = trailingComments;
+ }
+
+ if (leadingComments) {
+ module.leadingComments = leadingComments;
+ // get the last comment
+ const comment =
+ module.leadingComments[module.leadingComments.length - 1];
+
+ const hint = parseCompileHint(sourceFile, comment);
+
+ module.hint = hint;
+ }
+
+ return module;
})
);
diff --git a/core/deno_type_hint.test.ts b/core/deno_type_hint.test.ts
index 639cb41..44c0ed3 100644
--- a/core/deno_type_hint.test.ts
+++ b/core/deno_type_hint.test.ts
@@ -19,13 +19,14 @@ test("core / deno_type_hint: with compile hint", async () => {
`// @deno-types="./foo.d.ts"
import "./foo.ts"
`,
- ts.ScriptTarget.ESNext
+ ts.ScriptTarget.ESNext,
+ true,
+ ts.ScriptKind.TSX
);
const [comment] = getDenoCompileHint(ts)(sourceFile);
expect(comment).not.toBe(undefined);
- expect(comment.module).toEqual("./foo.d.ts");
- expect(comment.text).toEqual(`// @deno-types="./foo.d.ts"`);
+ expect(comment.text).toEqual(`./foo.d.ts`);
expect(comment.range).toEqual({
start: { line: 0, character: 0 },
end: { line: 0, character: 27 },
@@ -42,13 +43,14 @@ test("core / deno_type_hint: with compile hint", async () => {
`// @deno-types="/absolute/path/to/foo.d.ts"
import "./foo.ts"
`,
- ts.ScriptTarget.ESNext
+ ts.ScriptTarget.ESNext,
+ true,
+ ts.ScriptKind.TSX
);
const [comment] = getDenoCompileHint(ts)(sourceFile);
expect(comment).not.toBe(undefined);
- expect(comment.module).toEqual("/absolute/path/to/foo.d.ts");
- expect(comment.text).toEqual(`// @deno-types="/absolute/path/to/foo.d.ts"`);
+ expect(comment.text).toEqual(`/absolute/path/to/foo.d.ts`);
});
test("core / deno_type_hint: with compile hint 1", async () => {
@@ -62,13 +64,14 @@ test("core / deno_type_hint: with compile hint 1", async () => {
import "./foo.ts"
`,
- ts.ScriptTarget.ESNext
+ ts.ScriptTarget.ESNext,
+ true,
+ ts.ScriptKind.TSX
);
const [comment] = getDenoCompileHint(ts)(sourceFile);
expect(comment).not.toBe(undefined);
- expect(comment.module).toEqual("./foo.d.ts");
- expect(comment.text).toEqual(`// @deno-types="./foo.d.ts"`);
+ expect(comment.text).toEqual(`./foo.d.ts`);
expect(comment.range).toEqual({
start: { line: 3, character: 0 },
end: { line: 3, character: 27 },
@@ -93,13 +96,14 @@ test("core / deno_type_hint: with compile hint 2", async () => {
*/
/* prefix */ import "./foo.ts" // hasTrailingNewLine
`,
- ts.ScriptTarget.ESNext
+ ts.ScriptTarget.ESNext,
+ true,
+ ts.ScriptKind.TSX
);
const [comment] = getDenoCompileHint(ts)(sourceFile);
expect(comment).not.toBe(undefined);
- expect(comment.module).toEqual("./foo.d.ts");
- expect(comment.text).toEqual(`// @deno-types="./foo.d.ts"`);
+ expect(comment.text).toEqual(`./foo.d.ts`);
expect(comment.range).toEqual({
start: { line: 3, character: 0 },
end: { line: 3, character: 27 },
diff --git a/core/deno_type_hint.ts b/core/deno_type_hint.ts
index ed7c743..c34fec7 100644
--- a/core/deno_type_hint.ts
+++ b/core/deno_type_hint.ts
@@ -1,16 +1,5 @@
-import * as path from "path";
import typescript = require("typescript");
-import { normalizeFilepath } from "./util";
-
-interface CommentRange extends typescript.CommentRange {
- text: string;
- module: string;
- filepath: string;
- range: Range;
- contentRange: Range;
-}
-
export interface Position {
line: number;
character: number;
@@ -38,16 +27,53 @@ export const Range = {
},
};
+export type compileHint = {
+ text: string;
+ range: Range;
+ contentRange: Range;
+};
+
+export function parseCompileHint(
+ sourceFile: typescript.SourceFile,
+ comment: typescript.CommentRange
+): compileHint | undefined {
+ const text = sourceFile.getFullText().substring(comment.pos, comment.end);
+ const regexp = /@deno-types=['"]([^'"]+)['"]/;
+
+ const matchers = regexp.exec(text);
+
+ if (!matchers) {
+ return;
+ }
+
+ const start = sourceFile.getLineAndCharacterOfPosition(comment.pos);
+ const end = sourceFile.getLineAndCharacterOfPosition(comment.end);
+
+ const moduleNameStart = Position.create(
+ start.line,
+ start.character + '// @deno-types="'.length
+ );
+ const moduleNameEnd = Position.create(end.line, end.character - '"'.length);
+
+ const moduleName = matchers[1];
+
+ return {
+ text: moduleName,
+ range: Range.create(start, end),
+ contentRange: Range.create(moduleNameStart, moduleNameEnd),
+ };
+}
+
/**
* Get Deno compile hint from a source file
* @param ts
*/
export function getDenoCompileHint(ts: typeof typescript) {
- return function (sourceFile: typescript.SourceFile) {
- const denoTypesComments: CommentRange[] = [];
+ return function (sourceFile: typescript.SourceFile, pos = 0): compileHint[] {
+ const denoTypesComments: compileHint[] = [];
const comments =
- ts.getLeadingCommentRanges(sourceFile.getFullText(), 0) || [];
+ ts.getLeadingCommentRanges(sourceFile.getFullText(), pos) || [];
for (const comment of comments) {
if (comment.hasTrailingNewLine) {
@@ -59,34 +85,12 @@ export function getDenoCompileHint(ts: typeof typescript) {
const matchers = regexp.exec(text);
if (matchers) {
- const start = sourceFile.getLineAndCharacterOfPosition(comment.pos);
- const end = sourceFile.getLineAndCharacterOfPosition(comment.end);
-
- const moduleNameStart = Position.create(
- start.line,
- start.character + '// @deno-types="'.length
- );
- const moduleNameEnd = Position.create(
- end.line,
- end.character - '"'.length
- );
-
- const moduleName = matchers[1];
-
- const moduleFilepath = normalizeFilepath(moduleName);
-
- const targetFilepath = path.isAbsolute(moduleFilepath)
- ? moduleFilepath
- : path.resolve(path.dirname(sourceFile.fileName), moduleFilepath);
-
- denoTypesComments.push({
- ...comment,
- text,
- module: moduleName,
- filepath: targetFilepath,
- range: Range.create(start, end),
- contentRange: Range.create(moduleNameStart, moduleNameEnd),
- });
+ const compileHint = parseCompileHint(sourceFile, comment);
+
+ /* istanbul ignore else */
+ if (compileHint) {
+ denoTypesComments.push(compileHint);
+ }
}
}
}
diff --git a/core/hash_meta.ts b/core/hash_meta.ts
index 74378dd..1a13715 100644
--- a/core/hash_meta.ts
+++ b/core/hash_meta.ts
@@ -110,6 +110,9 @@ export class HashMeta implements HashMetaInterface {
case Type.JavaScriptReact:
return ".jsx";
case Type.TypeScript:
+ if (this.url.pathname.endsWith(".d.ts")) {
+ return ".d.ts";
+ }
return ".ts";
/* istanbul ignore next */
case Type.TypeScriptReact:
diff --git a/core/module_resolver.test.ts b/core/module_resolver.test.ts
index c36baae..a9df43b 100644
--- a/core/module_resolver.test.ts
+++ b/core/module_resolver.test.ts
@@ -147,7 +147,7 @@ test("core / module_resolver: resolve module from local", () => {
undefined,
undefined,
{
- extension: ".ts",
+ extension: ".d.ts",
origin: "https://example.com/x-typescript-types",
filepath: path.join(
denoDir,
diff --git a/examples/compile-hint/.vscode/settings.json b/examples/compile-hint/.vscode/settings.json
new file mode 100644
index 0000000..cbac569
--- /dev/null
+++ b/examples/compile-hint/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "deno.enable": true
+}
diff --git a/examples/compile-hint/foo.js b/examples/compile-hint/foo.js
new file mode 100644
index 0000000..4436ec1
--- /dev/null
+++ b/examples/compile-hint/foo.js
@@ -0,0 +1,5 @@
+export const foo = "foo";
+
+export function updateProfile() {
+ //
+}
diff --git a/examples/compile-hint/mod.ts b/examples/compile-hint/mod.ts
new file mode 100644
index 0000000..906a058
--- /dev/null
+++ b/examples/compile-hint/mod.ts
@@ -0,0 +1,10 @@
+// @deno-types="./types/foo.d.ts"
+import { foo, bar } from "./foo.js";
+
+function getName(name: string) {
+ console.log(name);
+}
+
+getName(foo);
+
+bar();
diff --git a/examples/compile-hint/types/foo.d.ts b/examples/compile-hint/types/foo.d.ts
new file mode 100644
index 0000000..c04adfe
--- /dev/null
+++ b/examples/compile-hint/types/foo.d.ts
@@ -0,0 +1,3 @@
+export const foo: string;
+
+export function bar(): void;
diff --git a/server/src/deno_types.ts b/server/src/deno_types.ts
index 16b50b7..b6a54c5 100644
--- a/server/src/deno_types.ts
+++ b/server/src/deno_types.ts
@@ -15,7 +15,7 @@ export function getDenoTypesHintsFromDocument(document: TextDocument) {
uri.fsPath,
document.getText(),
ts.ScriptTarget.ESNext,
- false,
+ true,
ts.ScriptKind.TSX
);
diff --git a/server/src/dependency_tree.ts b/server/src/dependency_tree.ts
index 0c9caee..cf2a58d 100644
--- a/server/src/dependency_tree.ts
+++ b/server/src/dependency_tree.ts
@@ -53,7 +53,7 @@ export class DependencyTree {
filepath,
await fs.readFile(filepath, { encoding: "utf8" }),
ts.ScriptTarget.ESNext,
- false,
+ true,
ts.ScriptKind.TSX
);
diff --git a/server/src/language/definition.ts b/server/src/language/definition.ts
index 7766142..ea8212a 100644
--- a/server/src/language/definition.ts
+++ b/server/src/language/definition.ts
@@ -8,7 +8,7 @@ import { TextDocument } from "vscode-languageserver-textdocument";
import { URI } from "vscode-uri";
import { getDenoTypesHintsFromDocument } from "../deno_types";
-import { pathExists } from "../../../core/util";
+import { ModuleResolver } from "../../../core/module_resolver";
export class Definition {
constructor(connection: IConnection, documents: TextDocuments) {
@@ -20,6 +20,10 @@ export class Definition {
return;
}
+ const uri = URI.parse(document.uri);
+
+ const resolver = ModuleResolver.create(uri.fsPath);
+
const locations: Location[] = [];
const denoTypesComments = getDenoTypesHintsFromDocument(document);
@@ -33,10 +37,11 @@ export class Definition {
position.character >= start.character &&
position.character <= end.character
) {
- if ((await pathExists(typeComment.filepath)) === true) {
+ const [typeModule] = resolver.resolveModules([typeComment.text]);
+ if (typeModule) {
locations.push(
Location.create(
- URI.file(typeComment.filepath).toString(),
+ URI.file(typeModule.filepath).toString(),
Range.create(0, 0, 0, 0)
)
);
diff --git a/server/src/language/diagnostics.ts b/server/src/language/diagnostics.ts
index b1b683e..e5865bc 100644
--- a/server/src/language/diagnostics.ts
+++ b/server/src/language/diagnostics.ts
@@ -18,7 +18,7 @@ import { Bridge } from "../bridge";
import { ModuleResolver } from "../../../core/module_resolver";
import { pathExists, isHttpURL, isValidDenoDocument } from "../../../core/util";
import { ImportMap } from "../../../core/import_map";
-import { getImportModules } from "../../../core/deno_deps";
+import { getImportModules, Range } from "../../../core/deno_deps";
type Fix = {
title: string;
@@ -136,7 +136,7 @@ export class Diagnostics {
uri.fsPath,
document.getText(),
ts.ScriptTarget.ESNext,
- false,
+ true,
ts.ScriptKind.TSX
);
@@ -145,10 +145,10 @@ export class Diagnostics {
const diagnosticsForThisDocument: Diagnostic[] = [];
const resolver = ModuleResolver.create(uri.fsPath, importMapFilepath);
- for (const importModule of importModules) {
- const [resolvedModule] = resolver.resolveModules([
- importModule.moduleName,
- ]);
+ const handle = async (originModuleName: string, location: Range) => {
+ const importModuleName = originModuleName;
+
+ const [resolvedModule] = resolver.resolveModules([importModuleName]);
if (
!resolvedModule ||
@@ -156,14 +156,12 @@ export class Diagnostics {
) {
const moduleName = resolvedModule
? resolvedModule.origin
- : ImportMap.create(importMapFilepath).resolveModule(
- importModule.moduleName
- );
+ : ImportMap.create(importMapFilepath).resolveModule(importModuleName);
if (isHttpURL(moduleName)) {
diagnosticsForThisDocument.push(
Diagnostic.create(
- importModule.location,
+ location,
localize(
"diagnostic.report.module_not_found_locally",
moduleName
@@ -173,7 +171,7 @@ export class Diagnostics {
this.name
)
);
- continue;
+ return;
}
console.log(moduleName);
@@ -185,7 +183,7 @@ export class Diagnostics {
) {
diagnosticsForThisDocument.push(
Diagnostic.create(
- importModule.location,
+ location,
localize(
"diagnostic.report.module_not_found_locally",
moduleName
@@ -195,13 +193,13 @@ export class Diagnostics {
this.name
)
);
- continue;
+ return;
}
// invalid module
diagnosticsForThisDocument.push(
Diagnostic.create(
- importModule.location,
+ location,
localize("diagnostic.report.invalid_import", moduleName),
DiagnosticSeverity.Error,
DiagnosticCode.InvalidImport,
@@ -209,6 +207,13 @@ export class Diagnostics {
)
);
}
+ };
+
+ for (const importModule of importModules) {
+ await handle(importModule.moduleName, importModule.location);
+ if (importModule.hint) {
+ await handle(importModule.hint.text, importModule.hint.contentRange);
+ }
}
return diagnosticsForThisDocument;
diff --git a/server/src/language/references.ts b/server/src/language/references.ts
index b30a0dd..229fe1e 100644
--- a/server/src/language/references.ts
+++ b/server/src/language/references.ts
@@ -8,7 +8,7 @@ import { TextDocument } from "vscode-languageserver-textdocument";
import { URI } from "vscode-uri";
import { getDenoTypesHintsFromDocument } from "../deno_types";
-import { pathExists } from "../../../core/util";
+import { ModuleResolver } from "../../../core/module_resolver";
export class References {
constructor(connection: IConnection, documents: TextDocuments) {
@@ -20,6 +20,10 @@ export class References {
return;
}
+ const uri = URI.parse(document.uri);
+
+ const resolver = ModuleResolver.create(uri.fsPath);
+
const locations: Location[] = [];
const denoTypesComments = getDenoTypesHintsFromDocument(document);
@@ -33,10 +37,11 @@ export class References {
position.character >= start.character &&
position.character <= end.character
) {
- if ((await pathExists(typeComment.filepath)) === true) {
+ const [typeModule] = resolver.resolveModules([typeComment.text]);
+ if (typeModule) {
locations.push(
Location.create(
- URI.file(typeComment.filepath).toString(),
+ URI.file(typeModule.filepath).toString(),
Range.create(0, 0, 0, 0)
)
);
diff --git a/typescript-deno-plugin/src/plugin.ts b/typescript-deno-plugin/src/plugin.ts
index fcea5ec..1ea22b6 100644
--- a/typescript-deno-plugin/src/plugin.ts
+++ b/typescript-deno-plugin/src/plugin.ts
@@ -11,6 +11,7 @@ import { CacheModule } from "../../core/deno_cache";
import { pathExistsSync, normalizeFilepath } from "../../core/util";
import { normalizeImportStatement } from "../../core/deno_normalize_import_statement";
import { readConfigurationFromVscodeSettings } from "../../core/vscode_settings";
+import { getImportModules } from "../../core/deno_deps";
export class DenoPlugin implements ts_module.server.PluginModule {
// plugin name
@@ -93,9 +94,6 @@ export class DenoPlugin implements ts_module.server.PluginModule {
this.MUST_OVERWRITE_OPTIONS
);
- this.logger.info(
- `compilationSettings:${JSON.stringify(compilationSettings)}`
- );
return compilationSettings;
};
@@ -363,6 +361,28 @@ export class DenoPlugin implements ts_module.server.PluginModule {
importMapsFilepath
);
+ const content = this.typescript.sys.readFile(containingFile, "utf8");
+
+ // handle @deno-types
+ if (content && content.indexOf("// @deno-types=") >= 0) {
+ const sourceFile = this.typescript.createSourceFile(
+ containingFile,
+ content,
+ this.typescript.ScriptTarget.ESNext,
+ true
+ );
+
+ const modules = getImportModules(this.typescript)(sourceFile);
+
+ for (const m of modules) {
+ if (m.hint) {
+ const index = moduleNames.findIndex((v) => v === m.moduleName);
+
+ moduleNames[index] = m.hint.text;
+ }
+ }
+ }
+
const resolvedModules = resolver.resolveModules(moduleNames);
// try resolve typeReferenceDirectives