Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure global type instances are available. #1175

Merged
merged 1 commit into from
Nov 9, 2018
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
3 changes: 2 additions & 1 deletion js/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import { assetSourceCode } from "./assets";
import * as deno from "./deno";
import { globalEval } from "./global_eval";
import { libdeno } from "./libdeno";
import { window } from "./globals";
import * as os from "./os";
import { RawSourceMap } from "./types";
import { assert, log, notImplemented } from "./util";

const window = globalEval("this");

const EOL = "\n";
const ASSETS = "$asset$";
const LIB_RUNTIME = "lib.deno_runtime.d.ts";
Expand Down
75 changes: 50 additions & 25 deletions js/globals.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,75 @@
// Copyright 2018 the Deno authors. All rights reserved. MIT license.
// This is a "special" module, in that it define the global runtime scope of
// Deno, and therefore it defines a lot of the runtime environemnt that code
// is evaluated in. We use this file to automatically build the runtime type
// library.

// Modules which will make up part of the global public API surface should be
// imported as namespaces, so when the runtime tpye library is generated they
// can be expressed as a namespace in the type library.
import * as blob from "./blob";
import * as consoleTypes from "./console";
import * as domTypes from "./dom_types";
import * as file from "./file";
import * as formdata from "./form_data";
import * as console_ from "./console";
import * as fetch_ from "./fetch";
import { Headers } from "./headers";
import { globalEval } from "./global_eval";
import { libdeno } from "./libdeno";
import * as formData from "./form_data";
import * as fetchTypes from "./fetch";
import * as headers from "./headers";
import * as textEncoding from "./text_encoding";
import * as timers from "./timers";
import * as urlSearchParams from "./url_search_params";
import * as domTypes from "./dom_types";

// These imports are not exposed and therefore are fine to just import the
// symbols required.
import { globalEval } from "./global_eval";
import { libdeno } from "./libdeno";

// During the build process, augmentations to the variable `window` in this
// file are tracked and created as part of default library that is built into
// deno, we only need to declare the enough to compile deno.

// Deno, we only need to declare the enough to compile Deno.
declare global {
const console: console_.Console;
const console: consoleTypes.Console;
const setTimeout: typeof timers.setTimeout;
// tslint:disable-next-line:variable-name
const TextEncoder: typeof textEncoding.TextEncoder;
}

// A reference to the global object.
export const window = globalEval("this");
// A self reference to the global object.
window.window = window;

window.setTimeout = timers.setTimeout;
window.setInterval = timers.setInterval;
window.clearTimeout = timers.clearTimer;
window.clearInterval = timers.clearTimer;

window.console = new console_.Console(libdeno.print);
window.TextEncoder = textEncoding.TextEncoder;
window.TextDecoder = textEncoding.TextDecoder;
// Globally available functions and object instances.
window.atob = textEncoding.atob;
window.btoa = textEncoding.btoa;
window.fetch = fetchTypes.fetch;
window.clearTimeout = timers.clearTimer;
window.clearInterval = timers.clearTimer;
window.console = new consoleTypes.Console(libdeno.print);
window.setTimeout = timers.setTimeout;
window.setInterval = timers.setInterval;

// When creating the runtime type library, we use modifications to `window` to
// determine what is in the global namespace. When we put a class in the
// namespace, we also need its global instance type as well, otherwise users
// won't be able to refer to instances.
// We have to export the type aliases, so that TypeScript _knows_ they are
// being used, which it cannot statically determine within this module.
window.Blob = blob.DenoBlob;
export type Blob = blob.DenoBlob;
window.File = file.DenoFile;
export type File = file.DenoFile;
window.URLSearchParams = urlSearchParams.URLSearchParams;
export type URLSearchParams = urlSearchParams.URLSearchParams;

window.fetch = fetch_.fetch;
// Using the `as` keyword to use standard compliant interfaces as the Deno
// implementations contain some implementation details we wouldn't want to
// expose in the runtime type library.
window.Headers = headers.Headers as domTypes.HeadersConstructor;
export type Headers = domTypes.Headers;
window.FormData = formData.FormData as domTypes.FormDataConstructor;
export type FormData = domTypes.FormData;

// using the `as` keyword to mask the internal types when generating the
// runtime library
window.Headers = Headers as domTypes.HeadersConstructor;
window.Blob = blob.DenoBlob;
window.File = file.DenoFile;
window.FormData = formdata.FormData as domTypes.FormDataConstructor;
// While these are classes, they have their global instance types created in
// other type definitions, therefore we do not have to include them here.
window.TextEncoder = textEncoding.TextEncoder;
window.TextDecoder = textEncoding.TextDecoder;
8 changes: 8 additions & 0 deletions js/headers_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,11 @@ test(function headerSymbolIteratorSuccess() {
assertEqual(value, headers.get(key));
}
});

test(function headerTypesAvailable() {
function newHeaders(): Headers {
return new Headers();
}
const headers = newHeaders();
assert(headers instanceof Headers);
});
3 changes: 3 additions & 0 deletions js/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
// Copyright 2018 the Deno authors. All rights reserved. MIT license.
// We need to make sure this module loads, for its side effects.
import "./globals";

import * as flatbuffers from "./flatbuffers";
import * as msg from "gen/msg_generated";
import { assert, log, setLogDebug } from "./util";
Expand Down
4 changes: 3 additions & 1 deletion js/repl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import * as deno from "./deno";
import { close } from "./files";
import * as dispatch from "./dispatch";
import { exit } from "./os";
import { window } from "./globals";
import { globalEval } from "./global_eval";

const window = globalEval("this");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doing that won't cause window to miss all properties assigned to it in globals.ts? I think we would like to use most of them in REPL

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid question ... However, I'm going to land this anyway because I need the fix and because this is passing the tests. If this indeed breaks window properties, we should add tests that nail that behavior down.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't just rebased it against my REPL branch and everything works as expected.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At compile/bundle time this ends up being an any, even when you do import { window } from "./globals.ts"; (because that reference is the same globalEval("this");. At runtime we build the type library which describes what the global scope is, and the ability to access the globalEval module isn't possible. I had previously thought about tightening compile time typings of globalEval("this"); which could be done, but it would be difficult and not really provide any safety, as most of what we do with it is cast it to any anyways.


function startRepl(historyFile: string): number {
const builder = flatbuffers.createBuilder();
Expand Down
2 changes: 1 addition & 1 deletion tests/error_003_typescript.ts.out
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[WILDCARD]~~~~~~

$asset$/lib.deno_runtime.d.tsILDCARD]
[WILDCARD]declare const console: console_.Console;
[WILDCARD]declare const console: consoleTypes.Console;
[WILDCARD]~~~~~~~
[WILDCARD]'console' is declared here.

2 changes: 1 addition & 1 deletion tests/error_008_checkjs.js.out
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[WILDCARD]~~~~~~

$asset$/lib.deno_runtime.d.tsILDCARD]
[WILDCARD]declare const console: console_.Console;
[WILDCARD]declare const console: consoleTypes.Console;
[WILDCARD]~~~~~~~
[WILDCARD]'console' is declared here.

11 changes: 5 additions & 6 deletions tools/ts_library_builder/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,16 @@ like this:
- This process assumes that all the modules that feed `js/deno.ts` will have a
public type API that does not have name conflicts.
- We process the `js/globals.ts` file to generate the global namespace.
- Currently we create a `"globals"` module which will contain the type
definitions.
- We create a `Window` interface and a `global` scope augmentation namespace.
- We iterate over augmentations to the `window` variable declared in the file,
extract the type information and apply it to both a global variable
declaration and a property on the `Window` interface.
- We identify any type aliases in the module and declare them globally.
- We take each namespace import to `js/globals.ts`, we resolve the emitted
declaration `.d.ts` file and create it as its own namespace withing the
`"globals"` module. It is unsafe to just flatten these, because there is a
high risk of collisions, but also, it makes authoring the types easier within
the generated interface and variable declarations.
declaration `.d.ts` file and create it as its own namespace within the global
scope. It is unsafe to just flatten these, because there is a high risk of
collisions, but also, it makes authoring the types easier within the generated
interface and variable declarations.
- We then validate the resulting definition file and write it out to the
appropriate build path.

Expand Down
16 changes: 16 additions & 0 deletions tools/ts_library_builder/ast_util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,22 @@ export function addSourceComment(
);
}

/** Add a declaration of a type alias to a node */
export function addTypeAlias(
node: StatementedNode,
name: string,
type: string,
hasDeclareKeyword = false,
jsdocs?: JSDoc[]
) {
return node.addTypeAlias({
name,
type,
docs: jsdocs && jsdocs.map(jsdoc => jsdoc.getText()),
hasDeclareKeyword
});
}

/** Add a declaration of a variable to a node */
export function addVariableDeclaration(
node: StatementedNode,
Expand Down
13 changes: 12 additions & 1 deletion tools/ts_library_builder/build_library.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import {
loadFiles,
logDiagnostics,
namespaceSourceFile,
normalizeSlashes
normalizeSlashes,
addTypeAlias
} from "./ast_util";

export interface BuildLibraryOptions {
Expand Down Expand Up @@ -216,6 +217,16 @@ export function mergeGlobal({
addInterfaceProperty(interfaceDeclaration, property, type);
}

// We need to copy over any type aliases
for (const typeAlias of sourceFile.getTypeAliases()) {
addTypeAlias(
targetSourceFile,
typeAlias.getName(),
typeAlias.getType().getText(sourceFile),
true
);
}

// We need to ensure that we only namespace each source file once, so we
// will use this map for tracking that.
const sourceFileMap = new Map<SourceFile, string>();
Expand Down
10 changes: 9 additions & 1 deletion tools/ts_library_builder/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,15 @@ test(function buildLibraryMerge() {
variableDeclarations[4].getType().getText(),
`typeof moduleD.reprocess`
);
assertEqual(variableDeclarations.length, 5);
assertEqual(
variableDeclarations[5].getType().getText(),
`typeof moduleC.Bar`
);
assertEqual(variableDeclarations.length, 6);
const typeAliases = targetSourceFile.getTypeAliases();
assertEqual(typeAliases[0].getName(), "Bar");
assertEqual(typeAliases[0].getType().getText(), "moduleC.Bar");
assertEqual(typeAliases.length, 1);
});

// TODO author unit tests for `ast_util.ts`
2 changes: 2 additions & 0 deletions tools/ts_library_builder/testdata/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ foobarbaz.bar = new moduleC.Bar();
foobarbaz.qat = moduleC.qat;
foobarbaz.process = moduleE.process;
foobarbaz.reprocess = moduleD.reprocess;
foobarbaz.Bar = moduleC.Bar;
export type Bar = moduleC.Bar;