Tslint: Indent rule not reporting or fixing indent size violations

Created on 23 May 2017  ·  66Comments  ·  Source: palantir/tslint

Bug Report

  • __TSLint version__: 5.3.2
  • __TypeScript version__: 2.3.2
  • __Running TSLint via__: CLI

TypeScript code being linted

export function foo() {
  return 123;
}

with tslint.json configuration:

{
  "extends": ["tslint:latest"],
  "rules": {
    "indent": {
      "options": ["spaces", 4]
    }
  }
}

Actual behavior

No errors with tslint. No fixes with tslint --fix.

Expected behavior

Errors reported with tslint, fixes applied with tslint --fix so that the resulting file looks like:

export function foo() {
    return 123;
}

2723 did not quite work like I expected. The problem seems to be that a failure is only reported if using the wrong whitespace character, not if the indent size is off (like in my example). relevant source.

Available in ESLint Formatting rule P2 Declined Bug

Most helpful comment

I'm experiencing the same issue. The rule below will catch tabs being used instead of spaces, but won't catch an incorrect number of spaces. I can change 2 to any other number, and I still get no errors. I'm using tslint 5.5.0.

"indent": [true, "spaces", 2],

All 66 comments

@adidahiya See this comment by @nchen63.

From @nchen63:

It fixes tabs -> x spaces and x spaces -> tabs, but doesn't fix x spaces -> y spaces

It SHOULD do x spaces to y spaces though.

My project uses both 2 and 4 spaces. Fix rule x spaces to y spaces would be very useful.

I also ran into this issue expecting it to fix size violations, or at least report them as errors so that I can fix them. At the moment it seems the configuration for the indent size documented on the site (https://palantir.github.io/tslint/rules/indent/) doesn't actually do anything.

Yes, please fix this bug. Angular CLI uses 2 spaces for an indentation level when generating a file or a project, and all quotes are single quotes. Then it runs tslint to fix these according to the user's tslint.json. Quotes work well (transforming to double quotes as per my preference), but indentation stays at 2 spaces (while I prefer 4). Tslint only reports an error if it sees an actual TAB character, but seems reasonable that it should also check the number of spaces

It seems to me like the implementation being used in https://github.com/palantir/tslint/blob/master/src/rules/indentRule.ts is quite naive, and I wouldn't expect it to work for x spaces -> y spaces. The approach seems to be fine with tabs, based on the comment here, but I can't see this approach as viable for spaces.

Say, for instance, an indentation width of 2 spaces was chosen, and consider this code:

foo = {
    a: {
  b: {
    c: 'c'
  }
    },
    d: 'd'
}

How would our current implementation know to fail this at all? Each sequence of spaces passes the regex / /. Even if we looked for multiples of 2 spaces, this would pass. Similarly, in multi-level indentation scenarios, 4 leading spaces could be a passing scenario, whereas beginning a single level of indentation with 4 spaces should fail.

I think any solution would need to traverse the AST, similar to what eslint does (https://github.com/eslint/eslint/blob/master/lib/rules/indent.js). The downside of that would be that we'd take a slight performance hit for tabs -> spaces or spaces -> tabs. We could get around that by choosing the implementation based on the settings, but I anticipate that the current implementation will fail if a combination of tabs and spaces are used, in which case we should only use the AST-based solution.

I think that at least some of the AST work done in the eslint implementation can be handled by typescript's library directly, so the solution shouldn't be too hard to write up.

How would our current implementation know to fail this at all?

It doesn't _technically_ need to. The align rule would prevent you from writing code like that. Sure, we could walk the AST in the indent rule as well. All I care about is that some combination of indent and align allow me to accomplish what I wrote in the issue description.

The align rule would prevent you from writing code like that.

Nice! I missed that when I was examining the source code. I'll take a peak over there too. TBH, I kinda like the idea of leveraging existing rules to keep others as naive as possible.

I'm experiencing the same issue. The rule below will catch tabs being used instead of spaces, but won't catch an incorrect number of spaces. I can change 2 to any other number, and I still get no errors. I'm using tslint 5.5.0.

"indent": [true, "spaces", 2],

Any updates on this? (If someone isn't working on a fix already, I'm willling to give it a go. )

@mDibyo go for it

@mDibyo Have you made any progress on this?

I have a partial implementation. Will try to complete it and put up a PR over the weekend

Just wanted to give a heads up that this issue has become a lower priority for me since I've started to use prettier formatting with tslint-plugin-prettier for my projects. We might try to add more official support for prettier in the built-in configs in the future and correspondingly adjust focus to core rules that are not formatting-related.

Prettier, on the other hand, strives to fit the most code into every line.

What if you don't want that?

With the print width set to 120, prettier may produce overly compact, or otherwise undesirable code.

We use 120 characters in our projects.


Also, it's yet another dependency. I think that I'd prefer to just use regular TSLint rules.

I think that I'd prefer to just use regular TSLint rules.

@glen-84 yeah, that's fine, which is why i'm not saying we should remove all formatting rules from TSLint and delegate completely to an external formatter. Prettier is obviously opinionated and not everyone is going to choose to use it. This issue is still open for PRs.

what's going on with this issue?

my pr is working in progress, to many cases, still need time. @cyberhck

so we expect it to get merged quite soon then :slightly_smiling_face:

Any update here?

Perhaps we could ask the typescript-formatter maintainers to expose a couple APIs? The package can already verify that an entire sourcefile is formatted according to settings in a tslint.json file

However, it may need help to play nice with TSLint's fixer setup.

The problem seems to be that a failure is only reported if using the wrong whitespace character, not if the indent size is off.

@adidahiya I can't get it to report an error if the wrong indentation character is used. If I set the rule to spaces/4 and have something like:

export function foo() {
return 123;
}

or

export function foo() {
<tab>return 123;
}

It doesn't report back an error. Per your original comment, are you sure it reports it if it's the wrong whitespace character?

Any advance on this? Just asking 😝

Advice? Use Prettier.

Has anyone tried tslint-eslint-rules?

@jscharett tslint-eslint-rules works with the ter-indent rule. Unfortunately it doesn't cover JSX indentation...

Any hope of a fix here?

This bug is still not fixed in v5.10.0

I gotta say, I don't imagine TSLint will ever be able to format JS code as well as Prettier. This is a complicated problem, and Prettier has solved it better than anyone else. I don't think we should be relying on TSLint for this, especially since projects often use both tools in tandem, and conflicts will likely arise...

EDIT: For a better idea of _how_ complicated this issue is, review this PR, or take a look at the Prettier source code. If these demonstrations don't strike you as "complicated", please help this project out with a PR 😄 !

@aervin I tend to disagree here. The 2 projects, in my opinion, have different purposes. Prettier falls into the category of formatting while TSLint is more in line with validating. Yes, TSLint can do formatting for some rules, but its intended purposes as a linter, goes back to validating.

The problem with relying on Prettier is that it is opinionated. That's great is you agree with its style, but what if you don't? We used to all use JSLint, and we all complained because it was so opinionated. Then came JSHint and JSCS, which gave us some control. Now we have powerful tools like @eslint which gave us the ability to plug-n-play and "fix" issues automatically.

While I am sure Prettier is a great project, I personally see it as a step backwards. Its taking control away from me. TSLint doesn't need to "fix" the code, if that is the issue, just flag it as an issue. I don't doubt that this issue is complicated, but eslint solved it. The rule used to work; what changed to break it?

@jscharett Thanks for your congenial response. I agree that these projects have different purposes, or _should_ have. My argument is that we should confine these projects to those purposes. Let's leave Prettier to fixing indentation problems, and let's leave TSLint to warning devs about arrow functions that can be simplified.

I also agree that Prettier is opinionated. I rather like that about Prettier. Now my team doesn't have to discuss whose formatting opinion is more reasonable. We can all complain about Prettier :laughing: .

EDIT:

The rule used to work; what changed to break it?

The opening issue comment leads me to believe this rule was never working as intended.

While I am sure Prettier is a great project, I personally see it as a step backwards. Its taking control away from me.

My experience was that I thought I cared about having fine-grained control over formatting rules right up until I added Prettier... and soon after I realized I didn't care so much about the particular way things were formatted, as the fact that they were formatted consistently. It's a huge cognitive load off not to worry about that any more, and concentrate entirely on what I want the code to _do_ rather than how it looks.

tslint already validates other things that would fall under the category of formatting. For example, enforcing alignment, bracket style, or spaces between variable names and operators. Additionally, it is desirable to be able to validate indentation without having to rely on an opinionated solution like prettier.

Needs less discussion, and more PR'ing. 😉😉

go ahead @ffxsam

My comment was mostly tongue-in-cheek. Though I'm wondering why this issue is over a year old and no progress has been made. Seems like everyone is just arguing about linting vs Prettier.

@ffxsam Because there's debate about whether tslint is more about the ts part or about the lint part

That's a valid point. Seems like there's some overlap with TSLint/ESLint. But the fact remains that there's a non-working indent option that has been broken for who knows how long. Seems like the fastest/easiest thing would be for someone familiar with the TSLint codebase to fix it...?

Vote up on fixing x spaces => y spaces. This is a feature our company relies very heavily on. It makes no sense just not fixing this.

@ffxsam I'm watching this issue for almost a year now, yeah it's been long, but as you can see there are two PR attempts, and it didn't work out till now, I think it's harder than it looks, but of course for maintainers it might be easier, I just have a lot of patience :slightly_smiling_face:

Still reproducible in the empty project
https://github.com/dimaShin/tslint-reproduce-2814

Hi @dimaShin , thanks for taking time to create a reproducible repository. However team is already aware of this issue, reproducing was never a problem.

We're just waiting on this feature, possibly with fix option, but that's not an issue for me. Last time I checked people were using prettier to check for indentation and tslint for everything else.

I'm not saying that'll be a good fit for you, for me it's certainly not, I'd also suggest using .editorconfig for this particular option and switch to tslint later when this is solved.

Again, thanks for taking time to add more info :)

Let's determine a strategy for checking indentation. For reference, here is the strategy that is used by eslint:

  1. An OffsetStorage instance stores a map of desired offsets, where each token has a specified offset from another specified token or to the first column.
  2. As the AST is traversed, modify the desired offsets of tokens accordingly. For example, when entering a BlockStatement, offset all of the tokens in the BlockStatement by 1 indent level from the opening curly brace of the BlockStatement.
  3. After traversing the AST, calculate the expected indentation levels of every token according to the OffsetStorage container.
  4. For each line, compare the expected indentation of the first token to the actual indentation in the file, and report the token if the two values are not equal.

This strategy is based on syntax, which, based on previous comments, is too opinionated because it requires lines to be broken in a certain way. I propose a simple way to check indentation that is independent of how the code is broken into lines:

  1. The unit of indentation is defined in the settings. For tabs, it is one tab character. For spaces, it is two or four space characters.
  2. The first non-empty line of the file should have zero units of indentation
  3. Every subsequent non-empty line should have either
    a. The same number of units of indentation as the preceding non-empty line or
    b. Less than the number of units of indentation as the preceding non-empty line or
    c. Exactly one more than the number of units of indentation as the preceding non-empty line

Some additional things to discuss:

  • The size parameter has no effect with tabs (why?)
  • There is no reason the size parameter can't be any positive integer
  • Auto-fixing the indentation size probably can't be implemented without going the syntax strategy route

@stifflerus Also rule should work with if/for/arrow function without {} blocks

@maximelkin Are the examples you gave not indented on the second line? Can you give an example of where the proposed strategy would fail?

if(this) that(); //okay because it's all one line

if(this)
  that(); //also okay because the second line is indented

let x = () => f(); //okay because it's all one line

let y = () =>
  f(); // I have not seen any code but like this but it would be okay

Would be awesome if this was fixed.

I can't believe something as fundamental as enforcing indentation rules is still not working.

So, there is no way to lint such ts-code in 2018?

const x = {
  a: 1,
   b: 2,
}

Works for me
./.eslintrc.ts.js:

module.exports = {
  'parser': 'typescript-eslint-parser',
  'parserOptions': {
    'ecmaVersion': 6,
    'sourceType': 'module',
    'ecmaFeatures': {
      'jsx': true,
    }
  },
  'plugins': [
    'react',
  ],
  'rules': {
    'indent': ['error', 2],
  },
}

yarn eslint --no-eslintrc --config ./.eslintrc.ts.js --ext .tsx src

https://github.com/eslint/typescript-eslint-parser

the solution i found for the indent problem in angular is to add prettier with the next steps:
npm install --save-dev tslint-plugin-prettier prettier tslint-jasmine-rules
edit tslint.json -->
` "rulesDirectory": [
"node_modules/codelyzer",
"node_modules/tslint-plugin-prettier",
"node_modules/tslint-jasmine-rules/dist"

],
"extends": "tslint-plugin-prettier",

"rules": {
"prettier": true,
//add here your wanted rules`

and in packege.json add -->
" ,"prettier": {
"singleQuote": true,
"printWidth": 140,
"semi": true,
"bracketSpacing": true,
"arrowParens": "always",
"parser": "typescript"
}"

This is a functionality that almost everybody uses on a daily basis. Would appreciate if this issue gets more attention.

Guys, You can use ter-indent option provided by tslint-eslint-rules.

"ter-indent": [ true, 4, { "SwitchCase": 1 }]

It worked for me. Cheers!

@hiteshaleriya that's what I've had in my project for a while now, but it isn't actually fixing the errors, just silencing them... here's my tslint.json:

{
    "extends": "tslint-config-airbnb",
    "rules": {
        "ter-indent": ["error", "spaces", 4],
        "no-unused-vars": ["warn"],
        "no-multi-spaces": false,
        "no-console": false,
        "max-line-length": false,
        "import-name": false
    }
}

and here's a relevant example that finished without causing warnings or errors:

function retrieveAndSetConfig(): Promise<any> {
  return new Promise((resolve, _) => {
  // ^ 2 spaces, expected 4
    const ghe = new GHEUtils();
    // ^ 4 spaces, expected 8
    // ...
}

It's also not showing errors when tabs are used (although that might be by design when 4 spaces are present?).

@SpencerKaiser Can you please update your ter-indent rule as shown below and then try:

"ter-indent": [true, 4]

I tried your example and it is working as expected at my end (causing errors).

@hiteshaleriya thanks for getting back to me so quickly! So it's now throwing errors as expected (👍) but it's not fixing any of them with --fix. Any ideas?

@SpencerKaiser Can you try running the --fix command twice. First time it will indent first line only then second time it will indent the rest (for your example code). Seems weird but please report the issue if it doesn't work.

@hiteshaleriya so a couple of observations... I didn't need to run it again, I needed to run it roughly n/4 times where n is the length of the indentation in spaces of the farthest indented line in the project ¯\_(ツ)_/¯

After finally finishing them all, it seems it's skipping over basic indentation errors like this:

class Something {
    function myFunc() {
        const myThing = {
            wat: 1,
         wattt: 5,    // 9 spaces, expected 12
        };
    }
}

If I mess up the indentation level of the const (line 17) to 0 spaces, the rest of it is flagged with errors _excluding_ the line with the space when I leave off --fix:

ERROR: 17:1  ter-indent  Expected indentation of 8 spaces but found 0.
ERROR: 18:1  ter-indent  Expected indentation of 4 spaces but found 12.
ERROR: 20:1  ter-indent  Expected indentation of 0 spaces but found 8.

With --fix, here's the first pass:

        const myThing = {
    wat: 1,
         wattt: 5,
};

And the second pass:

        const myThing = {
            wat: 1,
         wattt: 5,
        };

Thoughts??

@shubich I ended up doing the same thing...

Is there any update on this?

@MaKCbIMKo as far as I understand, the entire team will be moving to integrate eslint visit typescript-eslint, and in near future tslint will be deprecated, so it's as good as ignoring this rule for now (or use the tslint-config-prettier)

Closing due to the complexity of this task and the change in project direction: #4534

ESLint rule from typescript-eslint works perfectly for me (see #4534):

module.exports = {
    "env": {
        "browser": true,
    },
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaVersion": 2019,
        "sourceType": "module",
        "ecmaFeatures": {
            "jsx": true
        },
        "project": "./tsconfig.json",
    },
    "plugins": ["@typescript-eslint"],
    "rules": {
        "@typescript-eslint/indent": ["error", 2] // or ["error", "tab"]
    }
}

🤖 Beep boop! 👉 TSLint is deprecated 👈 and you should switch to typescript-eslint! 🤖

🔒 This issue is being locked to prevent further unnecessary discussions. Thank you! 👋

P.S. tslint-config-prettier - please stop using _linters_ such as TSLint to _format_ your TypeScript. That's better done by a _formatter_ such as Prettier.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mastrauckas picture mastrauckas  ·  46Comments

nrip-monotype picture nrip-monotype  ·  35Comments

sheam picture sheam  ·  31Comments

adidahiya picture adidahiya  ·  37Comments

adidahiya picture adidahiya  ·  52Comments