Tslint: variable-name: allow-leading-underscore does not allow for multiple underscores

Created on 6 Oct 2016  路  10Comments  路  Source: palantir/tslint

First a question to see if this is a a bug or not...

At the moment the allow-leading-underscore does not allow for multiple leading underscores. Is this an intentional decision for this rule to only allow a single leading underscore?

There is a closed issue (https://github.com/palantir/tslint/issues/189) from Aug 2014 about this same thing and the answer was to include the allow-leading-underscore option. So I don't know if the code has been modified since to change the behaviour of this rule?

Bug Report

  • TSLint version: 3.15.1
  • TypeScript version: 2.0.3
  • Running TSLint via: CLI

    TypeScript code being linted

private __form;

with tslint.json configuration:

"variable-name": [true,
    "check-format",
    "allow-leading-underscore"
]

Actual behavior

Fails with error:

variable name must be in camelcase or uppercase

Expected behavior

Should allow multiple underscores at the start of a variable

Declined Feature Request

Most helpful comment

double leading underscore for private members doesn't seem to be widely used in the typescript ecosystem. I doubt there will be a new option to enable this in variable-name.

If you really need this, you can try this one:
https://www.npmjs.com/package/tslint-consistent-codestyle#naming-convention

A sample configuration to enforce leading underscore on protected and double leading underscore on private members would be:

"naming-convention": [true,
  // this config will apply to properties AND methods, if you only need it for properties, use "property" instead of "member"
  {"type": "member", "format": "camelCase"}, // use camelCase for all members, will be inherited by protected and private
  {"type": "member", "modifiers": "protected", "leadingUnderscore": "require"}, // protected members will be REQUIRED to have a leading underscore. you can use "allow" as alternative
  {"type": "member", "modifiers": "private", "prefix": "__"} // to simply allow and not enforce double leading underscores, use "prefix": ["__", ""]
]

All 10 comments

I have also come across this issue (trying to use __fields as a variable name)

I have the same issue. I would like to mark obj._protectedVar and obj.__privateVar, this is only because that typeScipt generates javascript with publicly available private and protected variables. So such naming conventions can give visual information about its intentions.

double leading underscore for private members doesn't seem to be widely used in the typescript ecosystem. I doubt there will be a new option to enable this in variable-name.

If you really need this, you can try this one:
https://www.npmjs.com/package/tslint-consistent-codestyle#naming-convention

A sample configuration to enforce leading underscore on protected and double leading underscore on private members would be:

"naming-convention": [true,
  // this config will apply to properties AND methods, if you only need it for properties, use "property" instead of "member"
  {"type": "member", "format": "camelCase"}, // use camelCase for all members, will be inherited by protected and private
  {"type": "member", "modifiers": "protected", "leadingUnderscore": "require"}, // protected members will be REQUIRED to have a leading underscore. you can use "allow" as alternative
  {"type": "member", "modifiers": "private", "prefix": "__"} // to simply allow and not enforce double leading underscores, use "prefix": ["__", ""]
]

@ajafff Thank you for pointing extension that you are working on.

First of, "naming-covention" is a typo. And this was the time when i again realized how type doing a bad name in a programming world. Please don't get me wrong.

I've played more with plugin, and i liked implementation. I take ALL rules for naming-convention, so now my rule in tslint.json looks like now:

    "naming-convention": [

      true,
      {"type": "default", "format": "camelCase", "leadingUnderscore": "forbid", "trailingUnderscore": "forbid"},
      {"type": "variable", "modifiers": ["global", "const"], "format": "UPPER_CASE"},
      {"type": "parameter", "leadingUnderscore": "allow", "final": true},

      {"type": "member", "modifiers": "private", "leadingUnderscore": "allow", "prefix": "__"},
      {"type": "member", "modifiers": "protected", "leadingUnderscore": "require"},
      {"type": "method", "modifiers": "private", "leadingUnderscore": "allow", "prefix": "__"},
      {"type": "method", "modifiers": "protected", "leadingUnderscore": "require"},

      {"type": "property", "modifiers": ["public", "static", "const"], "format": "UPPER_CASE"},
      {"type": "type", "format": "PascalCase"},
      {"type": "class", "modifiers": "abstract", "prefix": "Abstract"},
      {"type": "interface", "prefix": "I"},
      {"type": "genericTypeParameter", "prefix": "T"},
      {"type": "enumMember", "format": "PascalCase"}

    ],

And again thank you very much!

To add,
Would be great if some rules could be optimized. For example:

      {"type": ["member",  "method"], "modifiers": "private", "leadingUnderscore": "allow", "prefix": "__"},
      {"type": ["member",  "method"], "modifiers": "protected", "leadingUnderscore": "require"},

@conmute glad I could help you and sorry for the inconvenience with the typo.

Would be great if some rules could be optimized.

Actually member is already the optimized version which includes property and method. There is no need to specify it again for method.

In addition, you need to slightly modify your config for private to make it work. You need to explicitly disable the leadingUnderscore check by setting it to a falsy value instead of allow. That's because "allow" would slice off the first leading underscore and then the prefix does not match anymore.

{"type": "member", "modifiers": "private", "leadingUnderscore": null, "prefix": "__"},

Looks like this is out of scope for the core ruleset and supported by tslint-consistent-codestyle. Closing for now but if there's a strong case for including this in TSLint core, please do speak up and we can re-open!

Any chance of at least allowing us to specify named exceptions to the rule -- as in specific variable names (or even types) that may be exempt from any restriction?

In GraphQL, we have __typename, in MongoDB, we have _id, etc. I have no control over how those fields are named, but I still want to type an eventual queried object as a whole, while still applying all the rules that should apply to my project (e.g. underscore prefixes not allowed in types WE create). Some of these fields are still very useful and actively used at runtime, and I would not want to introduce a translation phase just for the sake of pleasing the linter by circumventing decisions that are external to my project and out of my control.

The naming-convention solution is quite laborious. The solution could be much simpler, e.g. "variable-name": [true, "check-format", ["exceptions", "__typename", "_id"]] (having said that, I'd hate to see another ban-keywords).

Please, reconsider!

This may not be useful depending on if you have time to do some refactoring however...

Tslint is being deprecated in favour of integrating with ESLint (article) and ESLint has a rule for no-underscore-dangle, so updating to use typescript-eslint may help you sidestep this issue with TSLint.

I haven't made this change myself as I'm no longer working on the code base that needed the leading underscores, so not sure of the amount of work needed to port over to typescript-eslint but if you really need this rule then it might be the way forward.

Yeah, I moved to eslint a while back too.

I am still facing this issue

tslint.json

{
  "extends": "tslint:recommended",
  "rules": {
    "align": {
      "options": [
        "parameters",
        "statements"
      ]
    },
    "array-type": false,
    "arrow-return-shorthand": true,
    "curly": true,
    "deprecation": {
      "severity": "warning"
    },
    "component-class-suffix": true,
    "contextual-lifecycle": true,
    "directive-class-suffix": true,
    "directive-selector": [
      true,
      "attribute",
      "app",
      "camelCase"
    ],
    "component-selector": [
      true,
      "element",
      "app",
      "kebab-case"
    ],
    "eofline": true,
    "import-blacklist": [
      true,
      "rxjs/Rx"
    ],
    "import-spacing": true,
    "indent": {
      "options": [
        "spaces"
      ]
    },
    "max-classes-per-file": false,
    "max-line-length": [
      true,
      140
    ],
    "member-ordering": [
      true,
      {
        "order": [
          "static-field",
          "instance-field",
          "static-method",
          "instance-method"
        ]
      }
    ],
    "no-console": [
      true,
      "debug",
      "info",
      "time",
      "timeEnd",
      "trace"
    ],
    "no-empty": false,
    "no-inferrable-types": [
      true,
      "ignore-params"
    ],
    "no-non-null-assertion": true,
    "no-redundant-jsdoc": true,
    "no-switch-case-fall-through": true,
    "no-var-requires": false,
    "object-literal-key-quotes": [
      true,
      "as-needed"
    ],
    "quotemark": [
      true,
      "single"
    ],
    "semicolon": {
      "options": [
        "always"
      ]
    },
    "space-before-function-paren": {
      "options": {
        "anonymous": "never",
        "asyncArrow": "always",
        "constructor": "never",
        "method": "never",
        "named": "never"
      }
    },
    "typedef-whitespace": {
      "options": [
        {
          "call-signature": "nospace",
          "index-signature": "nospace",
          "parameter": "nospace",
          "property-declaration": "nospace",
          "variable-declaration": "nospace"
        },
        {
          "call-signature": "onespace",
          "index-signature": "onespace",
          "parameter": "onespace",
          "property-declaration": "onespace",
          "variable-declaration": "onespace"
        }
      ]
    },
    "variable-name": {
      "options": [
        "ban-keywords",
        "check-format",
        "allow-pascal-case"
      ]
    },
    "whitespace": {
      "options": [
        "check-branch",
        "check-decl",
        "check-operator",
        "check-separator",
        "check-type",
        "check-typecast"
      ]
    },
    "no-conflicting-lifecycle": true,
    "no-host-metadata-property": true,
    "no-input-rename": true,
    "no-inputs-metadata-property": true,
    "no-output-native": true,
    "no-output-on-prefix": true,
    "no-output-rename": true,
    "no-outputs-metadata-property": true,
    "template-banana-in-box": true,
    "template-no-negated-async": true,
    "use-lifecycle-interface": true,
    "use-pipe-transform-interface": true
  },
  "rulesDirectory": [
    "codelyzer"
  ]
}

Angular 9

Error message is

variable name must be in lowerCamelCase or UPPER_CASE

Was this page helpful?
0 / 5 - 0 ratings