Skip to content

Commit

Permalink
bugfix - no-use-before-declare - exception for destructuring w/ rename (
Browse files Browse the repository at this point in the history
palantir#3876)

* ignoring identifiers whose parent is a binding element

* check that ignored identifier is propname of binding element

* additional tests
  • Loading branch information
aervin_ authored and suchanlee committed May 30, 2018
1 parent 0bc48cd commit 2cd3dee
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 1 deletion.
71 changes: 71 additions & 0 deletions src/rules/code-examples/noUseBeforeDeclare.examples.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* @license
* Copyright 2013 Palantir Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as Lint from "../../index";

// tslint:disable: object-literal-sort-keys
export const codeExamples = [
{
description: "Check that referenced variables are declared beforehand (default)",
config: Lint.Utils.dedent`
"rules": { "no-use-before-declare": true }
`,
pass: Lint.Utils.dedent`
var hello = 'world';
var foo;
console.log(hello, foo, capitalize(hello));
// 'world', undefined, 'WORLD'
function capitalize(val) {
return val.toUpperCase();
}
import { default as foo1 } from "./lib";
import foo2 from "./lib";
import _, { map, foldl } from "./underscore";
import * as foo3 from "./lib";
import "./lib";
function declaredImports() {
console.log(foo1);
console.log(foo2);
console.log(foo3);
map([], (x) => x);
}
`,
fail: Lint.Utils.dedent`
console.log(hello, foo);
var hello = 'world';
var foo;
function undeclaredImports() {
console.log(foo1);
console.log(foo2);
console.log(foo3);
map([], (x) => x);
}
import { default as foo1 } from "./lib";
import foo2 from "./lib";
import _, { map, foldl } from "./underscore";
import * as foo3 from "./lib";
import "./lib";
`,
},
];
14 changes: 14 additions & 0 deletions src/rules/noUseBeforeDeclareRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@

import * as ts from "typescript";

import { isBindingElement } from "tsutils";
import * as Lint from "../index";
import { codeExamples } from "./code-examples/noUseBeforeDeclare.examples";

export class Rule extends Lint.Rules.TypedRule {
/* tslint:disable:object-literal-sort-keys */
Expand All @@ -38,6 +40,7 @@ export class Rule extends Lint.Rules.TypedRule {
type: "functionality",
typescriptOnly: false,
requiresTypeInfo: true,
codeExamples,
};
/* tslint:enable:object-literal-sort-keys */

Expand All @@ -60,6 +63,9 @@ function walk(ctx: Lint.WalkContext<void>, checker: ts.TypeChecker): void {
// Ignore `y` in `x.y`, but recurse to `x`.
return recur((node as ts.PropertyAccessExpression).expression);
case ts.SyntaxKind.Identifier:
if (isPropNameInBinding(node)) {
return;
}
return checkIdentifier(node as ts.Identifier, checker.getSymbolAtLocation(node));
case ts.SyntaxKind.ExportSpecifier:
return checkIdentifier(
Expand Down Expand Up @@ -92,4 +98,12 @@ function walk(ctx: Lint.WalkContext<void>, checker: ts.TypeChecker): void {
ctx.addFailureAtNode(node, Rule.FAILURE_STRING(node.text));
}
}

/**
* Destructured vars/args w/ rename are declared later in the source.
* var { x: y } = { x: 43 };
*/
function isPropNameInBinding(node: ts.Node): boolean {
return node.parent !== undefined && isBindingElement(node.parent) && node.parent.propertyName === node;
}
}
11 changes: 10 additions & 1 deletion test/rules/no-use-before-declare/test.ts.lint
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
subscribe(({ meta: newMeta }: {meta: SessionMeta}) => {
this.setCount(newMeta.match_count);
});

subscribe(({ meta: newMeta }) => this.setCount(meta.match_count));

$.x = 3;
~ [variable '$' used before declaration]
import $ = require("./$");
Expand Down Expand Up @@ -68,7 +74,10 @@ export {
};

var undeclaredA = 42;
let { undeclaredB } = { undeclaredB: 43 };
var { undeclaredB } = { undeclaredB: 43 };
var { undeclaredB: undeclaredA } = { undeclaredB: 43 };
var { undeclaredB: undeclaredA, undeclaredC } = { undeclaredB: 43, undeclaredC: 'hello' };
var { undeclaredB: undeclaredA, undeclaredC: undeclaredD } = { undeclaredB: 43, undeclaredC: 'hello' };
const [ undeclaredC, [undeclaredD] ] = [ 1, [2] ];

// shouldn't crash tslint
Expand Down

0 comments on commit 2cd3dee

Please sign in to comment.