-
Notifications
You must be signed in to change notification settings - Fork 47
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
Improve null safety #996
Improve null safety #996
Conversation
//if we don't have a rightmost range, use the leftmost range for both the start and end | ||
return this.createRangeFromPositions( | ||
leftmostRange.start, | ||
rightmostRange ? rightmostRange.end : leftmostRange.end); |
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.
This is changed behavior; previously an error would have been thrown in this case. I don't know whether this case would occur in practice.
} else { | ||
//this should never happen; somehow an invalid plugin has made it into here | ||
throw new Error(`TILT: Encountered an invalid plugin: ${String(plugin)}`); |
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.
I tend to sprinkle these assertions into my code, but they are often considered unidiomatic. The alternative here would be to remove the if
condition from the else if
above. Removing the condition could allow bad data to get farther into the program before it crashes, but so long as the type of theExport
(CompilerPlugin | CompilerPluginFactory
) is correct, no if
is needed in the else
.
src/parser/Expression.ts
Outdated
@@ -130,7 +130,7 @@ export class FunctionExpression extends Expression implements TypedefProvider { | |||
super(); | |||
if (this.returnTypeToken) { | |||
this.returnType = util.tokenToBscType(this.returnTypeToken); | |||
} else if (this.functionType.text.toLowerCase() === 'sub') { | |||
} else if (this.functionType && this.functionType.text.toLowerCase() === 'sub') { |
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.
Another case of "would previously throw an error in an edge case which will likely never happen"
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.
Can we use the optional chaining operator here instead?
} else if (this.functionType && this.functionType.text.toLowerCase() === 'sub') { | |
} else if (this.functionType?.text.toLowerCase() === 'sub') { |
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.
Fixed!
@@ -510,7 +512,7 @@ export class Util { | |||
* | bbb | bb | bbb | b | bbb | bb | bb | b | a | | |||
* ``` | |||
*/ | |||
public rangesIntersect(a: Range, b: Range) { | |||
public rangesIntersect(a: Range | undefined, b: Range | undefined) { |
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.
This and rangesIntersectOrTouch
are called with potentially undefined
values elsewhere but handle undefinedness gracefully internally, so I've loosened their type signatures.
@@ -732,7 +734,7 @@ export class Util { | |||
//this diagnostic is affected by this flag | |||
if (diagnostic.range && this.rangeContains(flag.affectedRange, diagnostic.range.start)) { | |||
//if the flag acts upon this diagnostic's code | |||
if (flag.codes === null || flag.codes.includes(diagnosticCode)) { | |||
if (flag.codes === null || (diagnosticCode !== undefined && flag.codes.includes(diagnosticCode))) { |
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.
Not a behavioral change, just satisfies the typechecker
@@ -1477,7 +1489,7 @@ export class Util { | |||
|
|||
public validateTooDeepFile(file: (BrsFile | XmlFile)) { | |||
//find any files nested too deep | |||
let pkgPath = file.pkgPath ?? file.pkgPath.toString(); | |||
let pkgPath = file.pkgPath ?? (file.pkgPath as any).toString(); |
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.
The type of file.pkgPath
is string
, so unless something unexpected has happened this toString()
call isn't necessary. Since it seems to only be relevant to cases where the types are wrong I don't think that an any
cast will hurt anything.
54d4acb
to
b49e776
Compare
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.
Looking great! I found a few things that need addressed, but overall I'm happy with these changes! Thanks a bunch.
src/parser/Expression.ts
Outdated
@@ -130,7 +130,7 @@ export class FunctionExpression extends Expression implements TypedefProvider { | |||
super(); | |||
if (this.returnTypeToken) { | |||
this.returnType = util.tokenToBscType(this.returnTypeToken); | |||
} else if (this.functionType.text.toLowerCase() === 'sub') { | |||
} else if (this.functionType && this.functionType.text.toLowerCase() === 'sub') { |
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.
Can we use the optional chaining operator here instead?
} else if (this.functionType && this.functionType.text.toLowerCase() === 'sub') { | |
} else if (this.functionType?.text.toLowerCase() === 'sub') { |
src/Program.spec.ts
Outdated
const expected: BsDiagnostic[] = [{ | ||
message: 'message', | ||
file: undefined, | ||
range: undefined | ||
}]; | ||
}] as any; |
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.
Couldn't you do it like this instead?
const expected: BsDiagnostic[] = [{ | |
message: 'message', | |
file: undefined, | |
range: undefined | |
}]; | |
}] as any; | |
const expected = [{ | |
message: 'message', | |
file: undefined, | |
range: undefined | |
}] as BsDiagnostic[] |
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.
I've changed it to:
const expected = [{
message: 'message',
file: undefined,
range: undefined
}] as any as BsDiagnostic[]
There has to be an any
somewhere. I could also do
const expected = [{
message: 'message',
file: undefined as any,
range: undefined as any
}] as BsDiagnostic[];
as an alternative
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.
Looking great! I found a few things that need addressed, but overall I'm happy with these changes! Thanks a bunch.
b49e776
to
82dabc3
Compare
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.
Looks great! One question, and then I think this is ready to go!
Looks great! Thanks for this. |
This adds a second tsconfig which can be used to view null safety errors, and fixes a handful of them. Nothing here should change runtime behavior and I've aimed to make the changes as noninvasive as possible.
I've added explicit type annotations and intermediate interfaces for the return types of some functions and methods which were previously implicit. In some cases this helps inference go through without changes and in other cases it just made it easier for me to determine what exactly was undefined in a nested object.
I've skipped a couple of the harder errors. Most notably, there seems to be an implicit distinction between an "input BsConfig" and an "initialized BsConfig", in that many fields in the BsConfig class are optional but assumed to be present during actual processing. The potentially pedantic way to address this would be to make a second
InitializedBsConfig
type which is used internally and is produced by parsing a BsConfig, but this would be significantly more invasive than what I am aiming for here.Please let me know if you would prefer any modifications to my approach.