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

Adds new rule "space-within-parens" #2959

Merged
merged 4 commits into from
Jul 21, 2017

Conversation

aminpaks
Copy link
Contributor

@aminpaks aminpaks commented Jun 26, 2017

PR checklist

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

Overview of change:

Adds new rule to verify amount of whitespace within parentheses.

Is there anything you'd like reviewers to focus on?

Everything.

CHANGELOG.md entry:

[new-rule] space-within-parens

@palantirtech
Copy link
Member

Thanks for your interest in palantir/tslint, @aminpaks! Before we can accept your pull request, you need to sign our contributor license agreement - just visit https://cla.palantir.com/ and follow the instructions. Once you sign, I'll automatically update this pull request.

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.

I left some suggestions to improve the algorithm.

Regarding failing CI: in addition to running yarn test on your local machine you should also run yarn lint and fix the reported failures. If you want all in one, you can use yarn verify.

@@ -0,0 +1,167 @@
/**
* @license
* Copyright 2013 Palantir Technologies, Inc.
Copy link
Contributor

Choose a reason for hiding this comment

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

2017

}

private checkAndAddFailureAt(position: number, length: number, spaceCount: number = 0) {
if (this.failures.some(f => f.getStartPosition().getPosition() === position)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

do you really need to remove duplicate failures here? If yes, there's probably something wrong with the rule

Copy link
Contributor Author

@aminpaks aminpaks Jun 27, 2017

Choose a reason for hiding this comment

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

With your algorithm how should I know that if this is not the case below? Both open and close paren tokens will add a failure. this is the only place that we need to check.

console.log( );
            ~ [open token]
            ~ [close token]

Copy link
Contributor

Choose a reason for hiding this comment

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

in my sample implementation there was a check if ch is a line break to avoid failures.

When checking the CloseBrace, you could add a similar check if ch === 40 to not fail if you reached the OpenBraceToken.

}

private getOpenParenToken(node: ts.Node): ts.Node | undefined {
return Lint.childOfKind(node, ts.SyntaxKind.OpenParenToken);
Copy link
Contributor

Choose a reason for hiding this comment

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

if you do this for every node, you could just use forEachToken from tsutils to visit every token instead of visiting every node and finding the paren tokens.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

forEachToken doesn't exist in tsutils v2.4.0.
I tried import { forEachToken } from 'tsutils'; no luck.

Copy link
Contributor

Choose a reason for hiding this comment

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

are you sure? I just tried it (v2.4.0) and it works for me.
Maybe it's another error, e.g. unused variable.

Copy link
Contributor Author

@aminpaks aminpaks Jun 27, 2017

Choose a reason for hiding this comment

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

Ah! it's utils not tsutils.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

forEachToken has this signature (fullText: string, kind: ts.SyntaxKind, pos: TokenPosition) I'm using not using fullText and Typescript is complaining. What should I do?

Copy link
Contributor

Choose a reason for hiding this comment

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

Well, I really meant import {forEachToken} from "tsutils". But the one in ../utils is also ok.

Re unused parameter: either use fullText instead of this.sourceFile.textor rename the parameter to_fullText` (not the leading underscore)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

nice!

@@ -0,0 +1,34 @@
// simple expression
var x = [{
Copy link
Contributor

Choose a reason for hiding this comment

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

add a test for multiline call expression. This should pass IMO

foo(
    bar,
    baz,
);

optionsDescription: Lint.Utils.dedent`
You may enforce the amount of whitespace within parentheses.
`,
options: {
Copy link
Contributor

Choose a reason for hiding this comment

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

simplify to

options: {type: "number", min: 0}

@@ -0,0 +1,5 @@
{
Copy link
Contributor

Choose a reason for hiding this comment

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

please rename the folder to nospace or something like that since there is no default for this rule


private checkOpenParenToken(node: ts.Node) {
const code = this.sourceFile.text.substr(node.getFullStart());
const matchResult = this.openingParenRegex.exec(code);
Copy link
Contributor

Choose a reason for hiding this comment

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

the regex solution seems a bit hacky and is not very easy to understand.
I'd prefer the following;

// pretending `node` is the OpenParenToken
let pos = node.end;
let ch = this.sourceFile.text.charCodeAt(pos);
while(ts.isWhiteSpaceSingleLine(ch)) {
    ++pos;
    ch = this.sourceFile.text.charCodeAt(pos);
}
const whitespaceCount = pos - node.end;
if (this.options.size === 0) {
    if (whitespaceCount !== 0)
        // TODO addFailure
} else if (!ts.isLineBreak(ch) /* don't require whitespace when followed by line break */ && whitespaceCount !== this.options.size) {
    // TODO fail
}

For the CloseParentToken you need a similar loop from node.pos - 1 backwards.

@aminpaks aminpaks force-pushed the space-within-parens branch 2 times, most recently from 095d197 to b3aa757 Compare June 29, 2017 00:35
@aminpaks
Copy link
Contributor Author

I forced push and applied all your comments/notes.
Please review.

@aminpaks aminpaks force-pushed the space-within-parens branch 2 times, most recently from 1313d78 to 7c62a81 Compare June 29, 2017 00:51
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.

It's getting better. But there are still some changes needed.
Please bear with me. The number of comments does not mean your code is bad

}

private checkNode(node: ts.Node): void {
forEachToken(node, (token: ts.Node) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I actually meant to replace the ts.forEachChild(...) in walk with this call.
You can just take this method's body and put it into walk.

With the current implementation the rule will visit nested tokens multiple times.

};
/* tslint:enable:object-literal-sort-keys */

public static FAILURE_NO_SPACE = "No whitespace within parentheses";
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you rephrase to Whitespace within parentheses is not allowed. or similar?

/* tslint:enable:object-literal-sort-keys */

public static FAILURE_NO_SPACE = "No whitespace within parentheses";
public static FAILURE_NEEDS_SPACE = "Needs whitespace within parentheses";
Copy link
Contributor

Choose a reason for hiding this comment

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

Needs x spaces within parentheses.

++currentPos;
currentChar = this.sourceFile.text.charCodeAt(currentPos);
}
if (!ts.isLineBreak(currentChar)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

if the rule is configured with 0 spaces, this condition will not show a warning for trailing whitespace right before a line break.
But that will be checked by trailing-whitespace anyway, so it's not strictly necessary here.

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 though about it too.

}

private checkCloseParenToken(tokenNode: ts.Node) {
let currentPos = tokenNode.getStart() - 1;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can be simplified to:

let currentPos = tokenNode.end - 2; // close paren token has a known width of 1, therefore node.getStart() === node.end - 1

Otherwise I would suggest to pass sourceFile as parameter to getStart for performance reasons.

};
}

class SpaceWhitinParensWalker extends Lint.AbstractWalker<Options> {
Copy link
Contributor

Choose a reason for hiding this comment

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

s/Whitin/Within/

}

public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithWalker(new SpaceWhitinParensWalker(sourceFile, this.ruleName, parseOptions(Number(this.ruleArguments[0]))));
Copy link
Contributor

Choose a reason for hiding this comment

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

remove the Number(...), that's done by parseOptions anyway

Copy link
Contributor Author

Choose a reason for hiding this comment

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

was getting Unsafe use of expression of type 'any'.

}
}

function parseOptions(whitespaceSize?: number): Options {
Copy link
Contributor

Choose a reason for hiding this comment

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

change to whitespaceSize?: any

* it's already been caught by `checkOpenParenToken`
*/
if (!ts.isLineBreak(currentChar) && currentChar !== 40) {
const whitespaceCount = tokenNode.end - tokenNode.pos - 1;
Copy link
Contributor

Choose a reason for hiding this comment

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

const whitespaceCount = token.end - currentPos - 2;

private checkCloseParenToken(tokenNode: ts.Node) {
let currentPos = tokenNode.getStart() - 1;
let currentChar = this.sourceFile.text.charCodeAt(currentPos);
const allowdSpaceCount = this.options.size;
Copy link
Contributor

Choose a reason for hiding this comment

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

allowedSpaceCount

@aminpaks
Copy link
Contributor Author

I pushed a commit. Please review :)

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.

LGTM with minor nits

lintMsg = Rule.FAILURE_NEEDS_SPACE(allowedSpaceCount - whitespaceCount);
const whitespace = " ".repeat(allowedSpaceCount - whitespaceCount);
lintFix = Lint.Replacement.appendText(position, whitespace);
} else if (whitespaceCount > allowedSpaceCount) {
Copy link
Contributor

Choose a reason for hiding this comment

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

you don't need this if, it's just the else branch

you can also remove the | undefined from the type of the variables above
and you can remove the if (lintMsg !== undefined) check below

}
}

private AddFailureAtWithFix(position: number, length: number, whitespaceCount: number = 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

method names should be lowerCamelCase

}
}

private AddFailureAtWithFix(position: number, length: number, whitespaceCount: number = 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

the default value for parameter whitespaceCount should not be necessary

@aminpaks
Copy link
Contributor Author

Done

@ajafff
Copy link
Contributor

ajafff commented Jul 21, 2017

Merging this PR, because it's open for quite some time, code looks good and there was no objection.

Thanks @aminpaks!

@ajafff ajafff merged commit 524c176 into palantir:master Jul 21, 2017
HyphnKnight pushed a commit to HyphnKnight/tslint that referenced this pull request Apr 9, 2018
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