-
Notifications
You must be signed in to change notification settings - Fork 5.4k
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
Support strict mode #505
Support strict mode #505
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,11 +37,14 @@ export function codeFetch( | |
fbs.Base.addMsgType(builder, fbs.Any.CodeFetch); | ||
builder.finish(fbs.Base.endBase(builder)); | ||
const resBuf = libdeno.send(builder.asUint8Array()); | ||
assert(resBuf != null); | ||
// Process CodeFetchRes | ||
const bb = new flatbuffers.ByteBuffer(new Uint8Array(resBuf)); | ||
// TypeScript does not track `assert` from a CFA perspective, therefore not | ||
// null assertion `!` | ||
const bb = new flatbuffers.ByteBuffer(new Uint8Array(resBuf!)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These null assert checks are redundant when using the bang operator. Suggest removing the assert. |
||
const baseRes = fbs.Base.getRootAsBase(bb); | ||
if (fbs.Any.NONE === baseRes.msgType()) { | ||
throw Error(baseRes.error()); | ||
throw Error(baseRes.error()!); | ||
} | ||
assert(fbs.Any.CodeFetchRes === baseRes.msgType()); | ||
const codeFetchRes = new fbs.CodeFetchRes(); | ||
|
@@ -80,7 +83,9 @@ export function codeCache( | |
const bb = new flatbuffers.ByteBuffer(new Uint8Array(resBuf)); | ||
const baseRes = fbs.Base.getRootAsBase(bb); | ||
assert(fbs.Any.NONE === baseRes.msgType()); | ||
throw Error(baseRes.error()); | ||
// undefined and null are incompatible in strict mode, but at runtime | ||
// a null value is fine, therefore not null assertion | ||
throw Error(baseRes.error()!); | ||
} | ||
} | ||
|
||
|
@@ -103,16 +108,22 @@ export function readFileSync(filename: string): Uint8Array { | |
builder.finish(fbs.Base.endBase(builder)); | ||
const resBuf = libdeno.send(builder.asUint8Array()); | ||
assert(resBuf != null); | ||
|
||
const bb = new flatbuffers.ByteBuffer(new Uint8Array(resBuf)); | ||
// TypeScript does not track `assert` from a CFA perspective, therefore not | ||
// null assertion `!` | ||
const bb = new flatbuffers.ByteBuffer(new Uint8Array(resBuf!)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These null assert checks are redundant when using the bang operator. Suggest removing the assert. |
||
const baseRes = fbs.Base.getRootAsBase(bb); | ||
if (fbs.Any.NONE === baseRes.msgType()) { | ||
throw Error(baseRes.error()); | ||
// undefined and null are incompatible in strict mode, but at runtime | ||
// a null value is fine, therefore not null assertion | ||
throw Error(baseRes.error()!); | ||
} | ||
assert(fbs.Any.ReadFileSyncRes === baseRes.msgType()); | ||
const res = new fbs.ReadFileSyncRes(); | ||
assert(baseRes.msg(res) != null); | ||
return new Uint8Array(res.dataArray()); | ||
const dataArray = res.dataArray(); | ||
assert(dataArray != null); | ||
// TypeScript cannot track assertion above, therefore not null assertion | ||
return new Uint8Array(dataArray!); | ||
} | ||
|
||
export function writeFileSync( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -57,7 +57,7 @@ export function setup(): void { | |
const mod = FileModule.load(filename); | ||
if (!mod) { | ||
util.log("getGeneratedContents cannot find", filename); | ||
return null; | ||
return ""; | ||
} | ||
return mod.outputCode; | ||
} | ||
|
@@ -71,7 +71,7 @@ export function setup(): void { | |
// FileModule.load(). FileModules are NOT executed upon first load, only when | ||
// compileAndRun is called. | ||
export class FileModule { | ||
scriptVersion: string; | ||
scriptVersion = ""; | ||
readonly exports = {}; | ||
|
||
private static readonly map = new Map<string, FileModule>(); | ||
|
@@ -105,15 +105,15 @@ export class FileModule { | |
execute(this.fileName, this.outputCode); | ||
} | ||
|
||
static load(fileName: string): FileModule { | ||
static load(fileName: string): FileModule | undefined { | ||
return this.map.get(fileName); | ||
} | ||
|
||
static getScriptsWithSourceCode(): string[] { | ||
const out = []; | ||
for (const fn of this.map.keys()) { | ||
const m = this.map.get(fn); | ||
if (m.sourceCode) { | ||
if (m && m.sourceCode) { | ||
out.push(fn); | ||
} | ||
} | ||
|
@@ -127,7 +127,8 @@ export function makeDefine(fileName: string): AmdDefine { | |
log("localRequire", x); | ||
}; | ||
const currentModule = FileModule.load(fileName); | ||
const localExports = currentModule.exports; | ||
util.assert(currentModule != null); | ||
const localExports = currentModule!.exports; | ||
log("localDefine", fileName, deps, localExports); | ||
const args = deps.map(dep => { | ||
if (dep === "require") { | ||
|
@@ -140,9 +141,13 @@ export function makeDefine(fileName: string): AmdDefine { | |
return deno; | ||
} else { | ||
const resolved = resolveModuleName(dep, fileName); | ||
const depModule = FileModule.load(resolved); | ||
depModule.compileAndRun(); | ||
return depModule.exports; | ||
util.assert(resolved != null); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These null assert checks are redundant when using the bang operator. Suggest removing the assert. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ry just to make it clear, the not null assertion operator ( As a side note, the TypeScript team recently closed the issue that would allow There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @kitsonk Oh sorry - I thought it threw an exception. In that case ignore my comments and keep the asserts. |
||
const depModule = FileModule.load(resolved!); | ||
if (depModule) { | ||
depModule.compileAndRun(); | ||
return depModule.exports; | ||
} | ||
return undefined; | ||
} | ||
}); | ||
factory(...args); | ||
|
@@ -156,10 +161,14 @@ export function resolveModule( | |
): null | FileModule { | ||
util.log("resolveModule", { moduleSpecifier, containingFile }); | ||
util.assert(moduleSpecifier != null && moduleSpecifier.length > 0); | ||
let filename: string, sourceCode: string, outputCode: string; | ||
let filename: string | null; | ||
let sourceCode: string | null; | ||
let outputCode: string | null; | ||
if (moduleSpecifier.startsWith(ASSETS) || containingFile.startsWith(ASSETS)) { | ||
// Assets are compiled into the runtime javascript bundle. | ||
const moduleId = moduleSpecifier.split("/").pop(); | ||
// we _know_ `.pop()` will return a string, but TypeScript doesn't so | ||
// not null assertion | ||
const moduleId = moduleSpecifier.split("/").pop()!; | ||
const assetName = moduleId.includes(".") ? moduleId : `${moduleId}.d.ts`; | ||
util.assert(assetName in assetSourceCode, `No such asset "${assetName}"`); | ||
sourceCode = assetSourceCode[assetName]; | ||
|
@@ -179,15 +188,17 @@ export function resolveModule( | |
sourceCode = fetchResponse.sourceCode; | ||
outputCode = fetchResponse.outputCode; | ||
} | ||
if (sourceCode == null || sourceCode.length === 0) { | ||
if (sourceCode == null || sourceCode.length === 0 || filename == null) { | ||
return null; | ||
} | ||
util.log("resolveModule sourceCode length ", sourceCode.length); | ||
const m = FileModule.load(filename); | ||
if (m != null) { | ||
return m; | ||
} else { | ||
return new FileModule(filename, sourceCode, outputCode); | ||
// null and undefined are incompatible in strict mode, but outputCode being | ||
// null here has no runtime behavior impact, therefore not null assertion | ||
return new FileModule(filename, sourceCode, outputCode!); | ||
} | ||
} | ||
|
||
|
@@ -204,7 +215,7 @@ function resolveModuleName( | |
} | ||
|
||
function execute(fileName: string, outputCode: string): void { | ||
util.assert(outputCode && outputCode.length > 0); | ||
util.assert(outputCode != null && outputCode.length > 0); | ||
window["define"] = makeDefine(fileName); | ||
outputCode += `\n//# sourceURL=${fileName}`; | ||
globalEval(outputCode); | ||
|
@@ -278,7 +289,7 @@ class TypeScriptHost implements ts.LanguageServiceHost { | |
getScriptVersion(fileName: string): string { | ||
util.log("getScriptVersion", fileName); | ||
const m = FileModule.load(fileName); | ||
return m.scriptVersion; | ||
return (m && m.scriptVersion) || ""; | ||
} | ||
|
||
getScriptSnapshot(fileName: string): ts.IScriptSnapshot | undefined { | ||
|
@@ -323,32 +334,40 @@ class TypeScriptHost implements ts.LanguageServiceHost { | |
const fn = "lib.globals.d.ts"; // ts.getDefaultLibFileName(options); | ||
util.log("getDefaultLibFileName", fn); | ||
const m = resolveModule(fn, ASSETS); | ||
return m.fileName; | ||
util.assert(m != null); | ||
// TypeScript cannot track assertions, therefore not null assertion | ||
return m!.fileName; | ||
} | ||
|
||
resolveModuleNames( | ||
moduleNames: string[], | ||
containingFile: string, | ||
reusedNames?: string[] | ||
): Array<ts.ResolvedModule | undefined> { | ||
containingFile: string | ||
): ts.ResolvedModule[] { | ||
//util.log("resolveModuleNames", { moduleNames, reusedNames }); | ||
return moduleNames.map((name: string) => { | ||
let resolvedFileName; | ||
if (name === "deno") { | ||
resolvedFileName = resolveModuleName("deno.d.ts", ASSETS); | ||
} else if (name === "typescript") { | ||
resolvedFileName = resolveModuleName("typescript.d.ts", ASSETS); | ||
} else { | ||
resolvedFileName = resolveModuleName(name, containingFile); | ||
if (resolvedFileName == null) { | ||
return undefined; | ||
return moduleNames | ||
.map(name => { | ||
let resolvedFileName; | ||
if (name === "deno") { | ||
resolvedFileName = resolveModuleName("deno.d.ts", ASSETS); | ||
} else if (name === "typescript") { | ||
resolvedFileName = resolveModuleName("typescript.d.ts", ASSETS); | ||
} else { | ||
resolvedFileName = resolveModuleName(name, containingFile); | ||
} | ||
} | ||
// This flags to the compiler to not go looking to transpile functional | ||
// code, anything that is in `/$asset$/` is just library code | ||
const isExternalLibraryImport = resolvedFileName.startsWith(ASSETS); | ||
return { resolvedFileName, isExternalLibraryImport }; | ||
}); | ||
// According to the interface we shouldn't return `undefined` but if we | ||
// fail to return the same length of modules to those we cannot resolve | ||
// then TypeScript fails on an assertion that the lengths can't be | ||
// different, so we have to return an "empty" resolved module | ||
// TODO: all this does is push the problem downstream, and TypeScript | ||
// will complain it can't identify the type of the file and throw | ||
// a runtime exception, so we need to handle missing modules better | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I agree There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Plan to deal with this under #509 |
||
resolvedFileName = resolvedFileName || ""; | ||
// This flags to the compiler to not go looking to transpile functional | ||
// code, anything that is in `/$asset$/` is just library code | ||
const isExternalLibraryImport = resolvedFileName.startsWith(ASSETS); | ||
// TODO: we should be returning a ts.ResolveModuleFull | ||
return { resolvedFileName, isExternalLibraryImport }; | ||
}); | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,10 +2,10 @@ | |
export type TypedArray = Uint8Array | Float32Array | Int32Array; | ||
|
||
export interface ModuleInfo { | ||
moduleName?: string; | ||
filename?: string; | ||
sourceCode?: string; | ||
outputCode?: string; | ||
moduleName: string | null; | ||
filename: string | null; | ||
sourceCode: string | null; | ||
outputCode: string | null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this not the same thing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Under strict mode, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok - makes sense |
||
} | ||
|
||
// Following definitions adapted from: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These null assert checks are redundant when using the bang operator. Suggest removing the assert.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ry @kitsonk FYI, you can avoid the bang if you create a new
assertNotNull
function like so:See this question on stackoverflow for more info: https://stackoverflow.com/a/46700791/266535
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@styfle that doesn't work the way you think it would work, because TypeScript does not know that the code past that point is narrowed without some block scoping. For example, if this is run under
--strictNullChecks
:Also see microsoft/TypeScript#8655 which explains that problem is not currently supported by TypeScript.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kitsonk Oh right, you would need an
if
statement for the assert for it to narrow the type as not null.See this playground.
But it's still safer than using
!
in my opinion 😅