stylelint: Fix "TypeError: opts.node.rangeBy is not a function" with PostCSS 8.4.4

What steps are needed to reproduce the bug?

folder structure: package.json .stylelintrc.json styles.scss (empty file)

package.json:

{
  "name": "untitled2",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "lint": "npx stylelint '**/*.scss'"
  },
  "devDependencies": {
    "stylelint": "^14.1.0",
    "stylelint-config-standard-scss": "^3.0.0"
  }
}

npm i npm run lint

What Stylelint configuration is needed to reproduce the bug?

{
  "extends": "stylelint-config-standard-scss"
}

How did you run Stylelint?

npx stylelint β€˜**/*.scss’

Which version of Stylelint are you using?

^14.1.0

What did you expect to happen?

I expect stylelint to show list of errors.

What actually happened?

TypeError: opts.node.rangeBy is not a function
at new Warning (node_modules/postcss/lib/warning.js:9:29)
at Result.warn (node_modules/postcss/lib/result.js:26:19)
at report (node_modules/stylelint/lib/utils/report.js:103:9)
at node_modules/stylelint/lib/rules/no-empty-source/index.js:28:3
at node_modules/stylelint/lib/lintPostcssResult.js:112:8
at Array.map (<anonymous>)
at lintPostcssResult (node_modules/stylelint/lib/lintPostcssResult.js:103:18)
at lintSource (node_modules/stylelint/lib/lintSource.js:88:8)
at async node_modules/stylelint/lib/standalone.js:218:27
at async Promise.all (index 0)

Process finished with exit code 1

The problem is in this line

image

Does the bug relate to non-standard syntax?

No

Proposal to fix the bug

Works correctly with exactly 8.3.11 version of postcss. I compared the code in warning.js between these versions of postcss (8.3.11 and 8.4.4), they changed the method name. 😦

Proposal: change dependencies of stylelint to use exactly 8.3.11 version.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 27
  • Comments: 71 (39 by maintainers)

Commits related to this issue

Most upvoted comments

Also started with a fresh install of stylelint@14.1.0 and stylelint-config-standard-scss@3.0.0. Ran into the same error described above. Installed postcss@8.4.4 as @BroFox86 did, and the error is gone.

Now let’s bump Stylelint with the latest PostCSS version and ask every user just to update stylelint in their package.json.

Thanks to @JounQin and @snebjorn for solving this very complex bug.

In my case I just added PostCSS to my project by npm i postcss and the error from the Stylelint VSC plugin was gone.

@snebjorn @JounQin @ai I appreciate your work very much! πŸ‘πŸΌ

Gentlemen, we have a winner!

❯ npm run stylelint

> stylelint
> stylelint src/**/*.less


src/foo.less
 2:22  βœ–  Unexpected whitespace at end of line  no-eol-whitespace

I have an idea, give me a weekend.

Here is a plan:

  1. Patch postcss-less to use PostCSS 8.4
  2. Add extra check for different AST version from parser to PostCSS core

hi! so users have to wait stylelint v14.9.2, yep?)

update: no, i’ve just updated postcss to 8.4.16 and it all works *___* thx a lot guys!

Thanks for the quick responses. It kept me motivated πŸ˜ƒ It’s been a pleasure!

@snebjorn exactly! I added the fix https://github.com/postcss/postcss/commit/f289e173b07bfb72265692662db94e5c49e5f583

I released a fix in PostCSS 8.4.15. Can we try it?

@JounQin The assignment of the rangeBy function is done here https://github.com/postcss/postcss/blob/main/lib/container.js#L415-L423 via Object.setPrototypeOf()

However the node that is throwing the error is of type root which the function doesn’t handle.

  if (node.type === 'atrule') {
    Object.setPrototypeOf(node, AtRule.prototype)
  } else if (node.type === 'rule') {
    Object.setPrototypeOf(node, Rule.prototype)
  } else if (node.type === 'decl') {
    Object.setPrototypeOf(node, Declaration.prototype)
  } else if (node.type === 'comment') {
    Object.setPrototypeOf(node, Comment.prototype)
  }

The function is being called for the root type. image

@ai could that be the issue?

So the key problem is postcss@8.3.5 which is called later does not have node.rangeBy right? Then it seems to be a new feature between minor versions from postcss?

@JounQin Yes, right. The rangeBy method has been added with PostCSS 8.4.0. See the changelog:

  • Added ranges for errors and warnings (by Adaline Valentina Simonian).

https://github.com/postcss/postcss/blob/main/CHANGELOG.md#84-president-camio

@ai Stylelint uses LazyResult to get parsing results. For example, when a user specifies customSyntax: 'postcss-less', the postcss-less syntax object is passed to new LazyResult(). See below:

https://github.com/stylelint/stylelint/blob/e424be5eca77c5532b13147c4a7dad14085a4276/lib/getPostcssResult.js#L40-L47

https://github.com/stylelint/stylelint/blob/e424be5eca77c5532b13147c4a7dad14085a4276/lib/getPostcssResult.js#L77

Given the following dependency tree (see the snebjorn/styelint-no-rangeBy repo),

/
β”œβ”€β”¬ postcss-less@6.0.0
β”‚ └── postcss@8.3.5 deduped
β”œβ”€β”€ postcss@8.3.5
└─┬ stylelint@14.9.1
  β”œβ”€β”¬ @csstools/selector-specificity@2.0.2
  β”‚ └── postcss@8.3.5 deduped
  β”œβ”€β”¬ postcss-safe-parser@6.0.0
  β”‚ └── postcss@8.3.5 deduped
  └── postcss@8.4.14

Then, LazyResult returns a result object including nodes produced by postcss@8.3.5 (postcss-less@6.0.0), while stylelint@14.9.1 tries to handle them as a node compatible with postcss@8.4.14 that has the rangeBy() method. However, since actual nodes don’t have rangeBy(), TypeError raises:

https://github.com/stylelint/stylelint/blob/e424be5eca77c5532b13147c4a7dad14085a4276/lib/utils/report.js#L31

We’re looking for how to absorb node differences between PostCSS versions.

I found it! The issue is with the custom syntax parsers. Here Stylelint is handing over control to PostCSS and it’s using the correct 8.4.14 version. The parser in this case is postcss-less.

image https://github.com/stylelint/stylelint/blob/9edf55135742be55fda8fac07566edf9c16241ba/lib/getPostcssResult.js#L77

Inside LazyResult we end up here. At this point we’re still using the nested PostCSS bundled with Stylelint. (This is good)

// omitted
} else {
  let parser = parse
  if (opts.syntax) parser = opts.syntax.parse
  if (opts.parser) parser = opts.parser
  if (parser.parse) parser = parser.parse

  try {
  root = parser(css, opts)    // here PostCSS hands off control to the parser `postcss-less`
  } catch (error) {
  // omitted

source: https://github.com/postcss/postcss/blob/e731697dffd2e14da609503a5114d3a28c948334/lib/lazy-result.js#L133

So far so good. But now it starts to go wrong. Because postcss-less import PostCSS.

const Input = require('postcss/lib/input'); // <--- imports PostCSS outside the scope of Stylelint

const LessParser = require('./LessParser');
const LessStringifier = require('./LessStringifier');

module.exports = {
  parse(less, options) {

source: https://github.com/shellscape/postcss-less/blob/master/lib/index.js#L3

But we’re no longer in the scope of Styellint. Looking at the stack track makes it very clear that we’re no longer β€œinside” stylelint

image

postcss-less have correctly declared PostCSS as a peer dependency

  "peerDependencies": {
    "postcss": "^8.3.5"
  },

source: https://github.com/shellscape/postcss-less/blob/984f4901aced4b74535d96b04242681d538d628b/package.json#L50

In my case, because I updated packages, I have this dependency graph

┬ postcss-less@6.0.0
└── postcss@8.3.6 deduped

A solution? I think the correct fix for this is to have Stylelint declare PostCSS as a peer dependency. That way Stylelint can increase the version of PostCSS and plugins that correctly list PostCSS as a peer dependency (with a range that compatible with Stylelint) will follow along.

I think I have part of the answer.

Stylelint it self have a dependency on @csstools/selector-specificity

https://github.com/stylelint/stylelint/blob/0d2c6cd98192b48d2cd5c8b305154aadd63a8a22/package.json#L111

@csstools/selector-specificity have a dependency on postcss@^8.2

https://github.com/csstools/postcss-plugins/blob/cb7490ed905947e6877fa7261dce8990ea24166f/packages/selector-specificity/package.json#L41

I have a project where I updated stylelint from an older version. The postcss version that satisficed ^8.2 when I first installed stylelint was postcss@8.3.6. When I went to update stylelint to 14.9.1 then my installed postcss@8.3.6 still satisficed postcss@^8.2 and thus I end up with this dependency graph

└─┬ stylelint@14.9.1
  β”œβ”€β”¬ @csstools/selector-specificity@2.0.2
  β”‚ └── postcss@8.3.6 deduped    <------------ NOTICE
  β”œβ”€β”¬ postcss-safe-parser@6.0.0
  β”‚ └── postcss@8.3.6 deduped    <------------ NOTICE
  └── postcss@8.4.14

So in my local install when a rule using @csstools/selector-specificity would trigger I’d end up with node.rangeBy is not a function because @csstools/selector-specificity@2.0.2 is using postcss@8.3.6 internally.

I’m still working on a minimal reproducible repo. But with the above dependency graph I got this error from a built-in stylelint rule

TypeError: node.rangeBy is not a function
    at report (~\myproject\node_modules\stylelint\lib\utils\report.js:31:34)
    at reportFromIndex (~\myproject\node_modules\stylelint\lib\rules\no-eol-whitespace\index.js:105:4)
    at ~\myproject\node_modules\stylelint\lib\rules\no-eol-whitespace\index.js:146:7
    at handleMatch (~\myproject\node_modules\style-search\index.js:201:5)
    at module.exports (~\myproject\node_modules\style-search\index.js:189:5)
    at eachEolWhitespace (~\myproject\node_modules\stylelint\lib\rules\no-eol-whitespace\index.js:133:4)
    at ~\myproject\node_modules\stylelint\lib\rules\no-eol-whitespace\index.js:114:3
    at ~\myproject\node_modules\stylelint\lib\lintPostcssResult.js:113:8
    at Array.map (<anonymous>)
    at lintPostcssResult (~\myproject\node_modules\stylelint\lib\lintPostcssResult.js:104:18)

This basically means that somewhere a node was created using postcss@8.3.6.

Also started with a fresh install of stylelint@14.1.0 and stylelint-config-standard-scss@3.0.0. Ran into the same error described above. Installed postcss@8.4.4 as @BroFox86 did, and the error is gone.

And moreover, you can then uninstall postcss and all works fine for current and all future projects. I wasn’t been able to reproduce the issue for any new projects as well.

Can you look npm ls for different PostCSS versions?

In our case we have a problem using https://github.com/ismay/stylelint-no-unsupported-browser-features/blob/master/package.json this package has postcss as a dependency

@ybiquitous

Edit: Never mind, it seems to be caused by stylelint-test-rule-tape.


Indeed, I’m surprised too. I’m getting postcss from stylelint and it is the latest of 8.4.21.

npm ls postcss
└─┬ stylelint@15.0.0
  β”œβ”€β”¬ @csstools/selector-specificity@2.1.1
  β”‚ └── postcss@8.4.21 deduped
  β”œβ”€β”¬ postcss-safe-parser@6.0.0
  β”‚ └── postcss@8.4.21 deduped
  └── postcss@8.4.21

FYI: That is my PR to upgrade my plugin to v15 https://github.com/AndyOGo/stylelint-declaration-strict-value/pull/310

@AndyOGo That error should never occur if you are using the latest PostCSS. Please recheck your dependency tree.

@ismay I mean it’s not required to upgrade to v14, you only need to upgrade the postcss dep on your side.

It’s just a matter of time before the next thing needs to be monkey patched, and so on.

I agree with this part. But it seems we only have this issue for node.rangeBy and we do rely on this prototype method to work correctly. Another solution would be that we just copy the rangeBy method into stylelint, but this has the same situation as you said.

I must mention the Peer Dependency solution again as it fixes everything.

The only bad thing about this solution is Yarn users will have to manually install PostCSS.

You know that it is not acceptable for yarn or pnpm or npm < 7, a large mount of users would be affected, not only the ones you expected.

I think monkey patching is a dangerous path to take. When looking through the code of Stylelint it’s not obvious that a different PostCSS version might be used at runtime. It’s just a matter of time before the next thing needs to be monkey patched, and so on.

If PostCSS can somehow guarantee that the postcssProcessor passed to it is the version used then all is well. Though I think that’ll be a tough one to crack. Though I haven’t given it much thought.

I must mention the Peer Dependency solution again as it fixes everything. I did a simple test where I wrapped Stylelint in a package with these peerDependencies

"peerDependencies": {
  "postcss": "^8.4.14",
  "stylelint": ">=14"
},

Using NPM v8.16.0 I installed my wrapper package npm i -D wrapped-stylelint and I ended up with this dependency graph

β”‚ β”œβ”€β”¬ postcss-less@6.0.0
β”‚ β”‚ └── postcss@8.4.14 deduped
β”‚ β”œβ”€β”€ postcss@8.4.14
└─┬ stylelint@14.9.1
  β”œβ”€β”¬ @csstools/selector-specificity@2.0.2
  β”‚ └── postcss@8.4.14 deduped
  β”œβ”€β”¬ postcss-safe-parser@6.0.0
  β”‚ └── postcss@8.4.14 deduped
  └── postcss@8.4.14 deduped

Note I didn’t install PostCSS. I just installed wrapped-stylelint. So the impact of this breaking change is minimal for NPM users since v7 (I didn’t actually test NPM v7)

The only bad thing about this solution is Yarn users will have to manually install PostCSS. But perhaps some traffic here https://github.com/yarnpkg/yarn/issues/1503 could change that

@JounQin Thanks for the comment. To be honest, I don’t know how to apply the way on https://github.com/stylelint/stylelint/issues/5766#issuecomment-1200504530… πŸ˜“

If PostCSS would provide a more elegant solution, I’m glad to adopt it.

I think the correct fix for this is to have Stylelint declare PostCSS as a peer dependency.

Thanks for your efforts first.

But I don’t think it’s acceptable for stylelint.

In details, stylelint is a singleton cli tool without any peer dependencies, changing postcss as peer would be a breaking change and changes a lot for end users.

It maybe something the package manager should address by deduplication, or you can try https://github.com/scinos/yarn-deduplicate#deduplication-strategies with fewer strategy.

I believe the simplest way to resolve the error is to install the latest versions of Stylelint and PostCSS.

I’m not sure if Stylelint should do something to address this problem. I wish anyone would provide a minimal reproduction repository to make investigation easier (for some package managers if possible)… πŸ˜“

It seems the error only happens when I comment using // instead of /* ?

I can confirm this. When I removed the // comments from my file the error stopped showing up.

It seems the error only happens when I comment using // instead of /* ?

https://user-images.githubusercontent.com/15921402/152132550-6c588314-bff4-499f-910a-2c579860647b.mp4

Thanks for the help.

I will try to find solution during this weekend.

I’ve created a reproduction repository, but such a TypeError never occurs. πŸ€” Is there something I missed?

https://github.com/ybiquitous/stylelint-issue-5766/runs/4451383596?check_suite_focus=true#step:6:1

image

thanks! I’ll try to clone your repo and see if it will work on my side. Then I will compare lock file and node_modules and to see what’s differs.

Could it happen because of the different PostCSS versions at chimera’s AST containing nodes from different PostCSS versions?

Can you look npm ls for different PostCSS versions?