Skip to content
This repository has been archived by the owner on Jul 15, 2023. It is now read-only.

convert no-unnecessary-field-initialization rule to use a walk function #804

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
99 changes: 52 additions & 47 deletions src/noUnnecessaryFieldInitializationRule.ts
@@ -1,5 +1,6 @@
import * as ts from 'typescript';
import * as Lint from 'tslint';
import * as tsutils from 'tsutils';

import { ExtendedMetadata } from './utils/ExtendedMetadata';
import { AstUtils } from './utils/AstUtils';
Expand All @@ -24,78 +25,45 @@ export class Rule extends Lint.Rules.AbstractRule {
};

public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithWalker(new UnnecessaryFieldInitializationRuleWalker(sourceFile, this.getOptions()));
return this.applyWithFunction(sourceFile, walk);
}
}

class UnnecessaryFieldInitializationRuleWalker extends Lint.RuleWalker {
private fieldInitializations: { [index: string]: string | undefined } = {};
function walk(ctx: Lint.WalkContext<void>) {
let fieldInitializations: { [index: string]: string | undefined } = {};

protected visitClassDeclaration(node: ts.ClassDeclaration): void {
this.fieldInitializations = {};
node.members.forEach(
(member: ts.ClassElement): void => {
if (member.kind === ts.SyntaxKind.PropertyDeclaration) {
this.visitPropertyDeclaration(<ts.PropertyDeclaration>member);
} else if (member.kind === ts.SyntaxKind.Constructor) {
this.visitConstructorDeclaration(<ts.ConstructorDeclaration>member);
}
}
);
this.fieldInitializations = {};
// do not call super.visitClass as a performance enhancement
}

protected visitPropertyDeclaration(node: ts.PropertyDeclaration): void {
const initializer = node.initializer;
if (node.name.kind === ts.SyntaxKind.Identifier) {
const fieldName: string = 'this.' + (<ts.Identifier>node.name).getText();
if (initializer === undefined) {
this.fieldInitializations[fieldName] = undefined;
} else if (AstUtils.isConstant(initializer)) {
this.fieldInitializations[fieldName] = initializer.getText();
}
}
if (initializer !== undefined && AstUtils.isUndefined(initializer)) {
// you should never initialize a field to undefined.
const start: number = initializer.getStart();
const width: number = initializer.getWidth();
this.addFailureAt(start, width, FAILURE_UNDEFINED_INIT + node.name.getText());
}
}

protected visitConstructorDeclaration(node: ts.ConstructorDeclaration): void {
function visitConstructorDeclaration(node: ts.ConstructorDeclaration): void {
if (node.body !== undefined) {
node.body.statements.forEach(
(statement: ts.Statement): void => {
if (statement.kind === ts.SyntaxKind.ExpressionStatement) {
const expression: ts.Expression = (<ts.ExpressionStatement>statement).expression;
if (expression.kind === ts.SyntaxKind.BinaryExpression) {
const binaryExpression: ts.BinaryExpression = <ts.BinaryExpression>expression;
if (tsutils.isExpressionStatement(statement)) {
const expression: ts.Expression = statement.expression;
if (tsutils.isBinaryExpression(expression)) {
const binaryExpression: ts.BinaryExpression = expression;

const property: ts.Expression = binaryExpression.left;
const propertyName: string = property.getText();
// check to see if a field is being assigned in the constructor
if (Object.keys(this.fieldInitializations).indexOf(propertyName) > -1) {
if (Object.keys(fieldInitializations).indexOf(propertyName) > -1) {
if (AstUtils.isUndefined(binaryExpression.right)) {
// field is being assigned to undefined... create error if the field already has that value
if (Object.keys(this.fieldInitializations).indexOf(propertyName) > -1) {
if (Object.keys(fieldInitializations).indexOf(propertyName) > -1) {
// make sure the field was declared as undefined
const fieldInitValue = this.fieldInitializations[propertyName];
const fieldInitValue = fieldInitializations[propertyName];
if (fieldInitValue === undefined) {
const start: number = property.getStart();
const width: number = property.getWidth();
this.addFailureAt(start, width, FAILURE_UNDEFINED_INIT + property.getText());
ctx.addFailureAt(start, width, FAILURE_UNDEFINED_INIT + property.getText());
}
}
} else if (AstUtils.isConstant(binaryExpression.right)) {
// field is being assigned a constant... create error if the field already has that value
const fieldInitValue = this.fieldInitializations[propertyName];
const fieldInitValue = fieldInitializations[propertyName];
if (fieldInitValue === binaryExpression.right.getText()) {
const start: number = binaryExpression.getStart();
const width: number = binaryExpression.getWidth();
const message: string = FAILURE_UNDEFINED_DUPE + binaryExpression.getText();
this.addFailureAt(start, width, message);
ctx.addFailureAt(start, width, message);
}
}
}
Expand All @@ -105,4 +73,41 @@ class UnnecessaryFieldInitializationRuleWalker extends Lint.RuleWalker {
);
}
}

function visitPropertyDeclaration(node: ts.PropertyDeclaration): void {
const initializer = node.initializer;
if (tsutils.isIdentifier(node.name)) {
const fieldName: string = 'this.' + node.name.getText();
if (initializer === undefined) {
fieldInitializations[fieldName] = undefined;
} else if (AstUtils.isConstant(initializer)) {
fieldInitializations[fieldName] = initializer.getText();
}
}
if (initializer !== undefined && AstUtils.isUndefined(initializer)) {
// you should never initialize a field to undefined.
const start: number = initializer.getStart();
const width: number = initializer.getWidth();
ctx.addFailureAt(start, width, FAILURE_UNDEFINED_INIT + node.name.getText());
}
}

function cb(node: ts.Node): void {
if (tsutils.isClassDeclaration(node)) {
fieldInitializations = {};
node.members.forEach(
(member: ts.ClassElement): void => {
if (tsutils.isPropertyDeclaration(member)) {
visitPropertyDeclaration(member);
} else if (tsutils.isConstructorDeclaration(member)) {
visitConstructorDeclaration(member);
}
}
);

fieldInitializations = {};
}
}

return ts.forEachChild(ctx.sourceFile, cb);
}