Skip to content
This repository has been archived by the owner on Mar 25, 2021. It is now read-only.

Rewrite no-unsafe-finally #2346

Merged
merged 5 commits into from
Apr 12, 2017
Merged

Conversation

andy-hanson
Copy link
Contributor

PR checklist

  • Addresses an existing issue: #0000
  • New feature, bugfix, or enhancement
    • Includes tests
  • Documentation update

Overview of change:

Just a rewrite. Should be simpler now.

Copy link
Contributor

@adidahiya adidahiya left a comment

Choose a reason for hiding this comment

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

just some minor comments

function isBreakBoundary(scope: IFinallyScope, node: ts.BreakStatement): boolean {
return node.label ? scope.labels.indexOf(node.label.text) >= 0 : scope.isBreakBoundary;
type JumpStatement = ts.BreakStatement | ts.ContinueStatement | ts.ThrowStatement | ts.ReturnStatement;
function showKind(node: JumpStatement): string {
Copy link
Contributor

Choose a reason for hiding this comment

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

how about naming this printJumpKind

if (isControlFlowBoundary(currentScope, node)) {
return false;
function jumpIsLocalToFinallyBlock(jump: JumpStatement): boolean {
const isBreakContinue = jump.kind === ts.SyntaxKind.BreakStatement || jump.kind === ts.SyntaxKind.ContinueStatement;
Copy link
Contributor

Choose a reason for hiding this comment

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

isBreakOrContinue

}

function isBreakBoundary(scope: IFinallyScope, node: ts.BreakStatement): boolean {
return node.label ? scope.labels.indexOf(node.label.text) >= 0 : scope.isBreakBoundary;
type JumpStatement = ts.BreakStatement | ts.ContinueStatement | ts.ThrowStatement | ts.ReturnStatement;
Copy link
Contributor

Choose a reason for hiding this comment

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

let's move this type to just before function walk since it's used above too

case ts.SyntaxKind.TryStatement:
const { tryBlock, catchClause, finallyBlock } = node as ts.TryStatement;
ts.forEachChild(tryBlock, cb);
if (catchClause) {
Copy link
Contributor

Choose a reason for hiding this comment

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

try to avoid implicit boolean coercion, check catchClause !== undefined instead. same a few lines down

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We do have a lint rule for that (strict-boolean-expressions), would you want to enable that in this repo?

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah, I'd like to enable it, I remember having some issues when trying to use it elsewhere but I haven't put in the time to investigate it fully.

Copy link
Contributor

@ajafff ajafff left a comment

Choose a reason for hiding this comment

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

Your implementation is missing checks to prevent crashes on invalid code and proper handling of function boundaries. Added comments at the relevant sections of code.

break;

default:
ts.forEachChild(node, cb);
Copy link
Contributor

Choose a reason for hiding this comment

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

if (isFunctionScopeBoundary(node)) {
    const old = inFinally;
    inFinally = false;
    ts.forEachChild(node, cb);
    inFinally = old;
    break;
} else {
    return ts.forEachChild(node, cb);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think that's necessary. jumpIsLocalToFinallyBlock checks for function scopes.


let node: ts.Node = jump;
// This should only be called inside a finally block, so we'll eventually reach the TryStatement case and return.
while (true) {
Copy link
Contributor

@ajafff ajafff Mar 27, 2017

Choose a reason for hiding this comment

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

Avoid property access on undefined by ending the loop at SourceFile.
You should also stop at function scope boundary.

while(node.kind !== ts.SyntaxKind.SourceFile && !isFunctionScopeBoundary(node))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We'll always exit this loop, as we only get here if we saw a TryStatement above.

case ts.SyntaxKind.ClassExpression:
case ts.SyntaxKind.FunctionDeclaration:
case ts.SyntaxKind.FunctionExpression:
case ts.SyntaxKind.ArrowFunction:
Copy link
Contributor

Choose a reason for hiding this comment

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

this does not include all function scope boundaries. use isFunctionScopeBoundary(parent) instead, see comment above

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

return false;
function jumpIsLocalToFinallyBlock(jump: JumpStatement): boolean {
const isBreakOrContinue = jump.kind === ts.SyntaxKind.BreakStatement || jump.kind === ts.SyntaxKind.ContinueStatement;
const label = isBreakOrContinue ? (jump as ts.BreakStatement | ts.ContinueStatement).label : undefined;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: ts.BreakOrContinueStatement

ctx.addFailureAtNode(node, Rule.FAILURE_STRING(printJumpKind(node as JumpStatement)));
}
ts.forEachChild(node, cb);
break;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: return ts.forEachChild(...)

case ts.SyntaxKind.ClassExpression:
case ts.SyntaxKind.FunctionDeclaration:
case ts.SyntaxKind.FunctionExpression:
case ts.SyntaxKind.ArrowFunction:
return true;
Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't this return false?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If you're climbing parents and see a function before you reach the try statement, that means there is a function inside the finally block. Jump statements can't leave a function, so the jump is definitely local to that finally block (because it's local to the function and the function is inside the finally block).

Copy link
Contributor

@ajafff ajafff left a comment

Choose a reason for hiding this comment

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

just one change request and a comment.
other than that the code looks good.


let node: ts.Node = jump;
// This should only be called inside a finally block, so we'll eventually reach the TryStatement case and return.
while (true) {
Copy link
Contributor

Choose a reason for hiding this comment

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

you still need to check if you reached ts.SourceFile to guard against invalid code crashing this rule.
take this code for example:

try {
} finally {
    break; // break is not allowed here, tsc will complain but this rule will crash
}
// or
for(;;) {
    try {
    } finally {
        break foo; // label does not exist, tsc will complain but this rule will crash
    }
}

Copy link
Contributor Author

@andy-hanson andy-hanson Apr 8, 2017

Choose a reason for hiding this comment

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

Added a test case. (It doesn't crash.)

// falls through

default:
return ts.forEachChild(node, cb);
Copy link
Contributor

Choose a reason for hiding this comment

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

you're right, you don't need to check for function boundaries here, but it would avoid doing unnecessary time spent in jumpIsLocalToFinallyBlock and would make the code clearer.
You can of course avoid doing the additional check if inFinally === false.

if (inFinally && isFunctionScopeBoundary(node)) {
    inFinally = false;
    ts.forEachChild(node, cb);
    inFinally = true;
} else {
    return ts.forEachChild(node, cb);
}

Copy link
Contributor Author

@andy-hanson andy-hanson Apr 8, 2017

Choose a reason for hiding this comment

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

jumpIsLocalToFinallyBlock is only activated if a jump statement appears in a finally block, which with 99% certainty won't happen if people are linting with this rule. (Who on earth would put a loop with a break statement inside of a finally block?) So I don't think it's worth doing more work in cb (which runs on every node) to try to save time in jumpIsLocalToFinallyBlock (which will almost never be called).

@adidahiya adidahiya merged commit 39e3f20 into palantir:master Apr 12, 2017
@andy-hanson andy-hanson deleted the no-unsafe-finally branch April 13, 2017 00:23
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants