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

feat: support deno-types compile hint #129

Merged
merged 7 commits into from
Apr 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
25 changes: 23 additions & 2 deletions core/deno_deps.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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([
Expand All @@ -57,6 +59,16 @@ export * as xx from "export_as_default.ts";
},
},
{
hint: undefined,
leadingComments: [
{
end: 97,
hasTrailingNewLine: true,
kind: 2,
pos: 0,
text: `/// <reference types="https://raw.githubusercontent.com/date-fns/date-fns/master/typings.d.ts" />`,
},
],
moduleName: "./foo.ts",
location: {
start: { line: 1, character: 8 },
Expand Down Expand Up @@ -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[]);
});
75 changes: 73 additions & 2 deletions core/deno_deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -70,9 +75,18 @@ export async function getAllDenoCachedDeps(): Promise<Deps[]> {
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) {
Expand Down Expand Up @@ -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) => {
Expand All @@ -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;
})
);

Expand Down
28 changes: 16 additions & 12 deletions core/deno_type_hint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand All @@ -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 () => {
Expand All @@ -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 },
Expand All @@ -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 },
Expand Down
88 changes: 46 additions & 42 deletions core/deno_type_hint.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions core/hash_meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion core/module_resolver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions examples/compile-hint/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"deno.enable": true
}
Loading