npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

coffeescript-cognitive-complexity

v1.0.0

Published

Native Cognitive Complexity (SonarSource S3776) analyzer for CoffeeScript — measures the source AST, not compiled JS.

Readme

coffeescript-cognitive-complexity

Cognitive Complexity (SonarSource / G. Ann Campbell, rule S3776) for CoffeeScript, measured natively on the CoffeeScript source AST — not by compiling to JS first.

Cognitive Complexity estimates how hard code is to understand (unlike Cyclomatic Complexity, which measures testability). Three rules: ignore readable shorthand; +1 for each break in linear flow (if/loop/switch/catch/logical-operator sequences/recursion); +1 more for each level such a structure is nested.

npm install --save-dev coffeescript-cognitive-complexity
# CLI (a directory is walked recursively for *.coffee):
npx coffee-cc --top=20 src/
npx coffee-cc --explain path/to/File.coffee
npx coffee-cc --by-class src/
// API:
const { analyzeSource } = require('coffeescript-cognitive-complexity');
const r = analyzeSource(coffeeSource, 'File.coffee');
// → { file, functions:[{name,klass,line,complexity,increments}], fileTop, total, classes }

Why native, not compile-to-JS?

There is no other cognitive-complexity tool for CoffeeScript — coffeelint and escomplex-coffee only do cyclomatic complexity (decision counting, no nesting penalty), and every real cognitive-complexity implementation (eslint-plugin-sonarjs, cognitive-complexity-ts, SonarQube…) targets JS/TS/Python and can't parse .coffee.

You can compile .coffee to JS and score that — but it skews upward and loses source line attribution, because CoffeeScript's readable shorthands expand into exactly the control-flow structures the metric penalizes. Same snippets, this tool (native) vs. the canonical eslint-plugin-sonarjs and ccts on the compiled JS:

  native | sonarjs | ccts  | case
  -------|---------|-------|------------------------------------------------
       0 |       6 |     6 | a?.b?.c?.d              soak chain → nested null-guards
       0 |       1 |     1 | x = a ? b               existential → a != null ? a : b
       0 |       1 |     1 | @x ?= def()             existential assign → if (x == null)
       3 |       8 |     8 | (u?.name for u in xs when u?.active)   sugar on sugar
       6 |       7 |     7 | plain control flow      implicit-return `for` injects an else
       1 |       1 |     1 | unless / switch / chained ternary … (no sugar → all agree)

The two compiled-JS tools agree with each other everywhere and with native wherever there's no CoffeeScript sugar — which is what shows the divergences are genuine sugar-skew (the spec says to ignore null-coalescing), not tool error. Reproduce with examples/skew-comparison.js (needs eslint, eslint-plugin-sonarjs, cognitive-complexity-ts installed alongside).


Granularity: method → class → file

Cognitive Complexity is a per-method metric — that is the actionable unit (which function to refactor; SonarQube gates each method at 15 by default). Because it charges no "cost of entry" per method (an empty method scores 0, not 1), summing methods up to the class or file level is meaningful: a high class total distinguishes a god-class with tangled control flow from a large-but-simple class of getters/setters — something a cyclomatic total (which just tracks method count / LOC) cannot do.

  • default — every function/method ranked by its own complexity
  • --explain — the per-increment breakdown within a method
  • --by-class / --by-file — the sums
  • --json — all of the above, machine-readable

CLI options: --explain, --json, --top=N (0 = all), --threshold=N, --by-file, --by-class, --sort=complexity|name|line, --comprehensions=loop|shorthand|off.


What it counts

Following S3776 (Appendix B), against the CoffeeScript AST:

| Construct | Increment | |---|---| | if / ternary (value-position if…then…else) | +1, + nesting | | else / else if | +1 (flat) | | switch (whole statement, once) | +1, + nesting | | for / comprehension, while / until / loop | +1, + nesting | | catch | +1, + nesting | | sequence of like and/or (&&/||) operators | +1 per run (flat) | | direct recursion | +1 (flat) | | nested function / lambda | no increment, but +1 nesting for its body |

Top-level methods get no cost of entry; nesting resets to 0 in each.

CoffeeScript-specific decisions

  • Null-coalescing is ignored (the spec's Rule 1): the existential operator a ? b, soaks a?.b, and ?= / or= / and= compound assigns contribute 0. This is the main fidelity win over scoring compiled JS, where they become counted if/&& guards.
  • unless and postfix if/while/until are the same AST nodes as their prefix forms and count normally.
  • Ternary vs. statement-if is decided the way the CoffeeScript compiler decides a?b:c vs. if(){}else{}: an if…then…else is a ternary (a single increment, no separate else) only when its value is consumed in expression position and both branches are single expressions; a function-tail or standalone if/else is a statement (if +1, else/else if +1 each). Verified against CoffeeScript.compile output. A nested ternary in an alternate position does receive a nesting increment (spec B3 lists "ternary operator"; it excludes "else if") — confirmed against eslint-plugin-sonarjs.
  • Comprehensions — --comprehensions:
    • loop (default): a comprehension is a loop → +1 (+nesting); its when guard → +1 more.
    • shorthand: like .filter().map() — the iteration is free; only real control flow / logical sequences inside still count.
    • off: comprehensions contribute nothing.
  • Bare super (CoffeeScript-1 argument-forwarding form, which vanilla CoffeeScript 2 rejects) is rewritten to super() before parsing — a complexity-neutral shim, on by default (superShim option), a no-op on code without it.
  • Labeled jumps (goto/break LABEL), a spec increment category, don't exist in CoffeeScript, so that category never applies.

Validation

npm test runs 23 cases, including the SonarSource white paper's own worked examples translated to CoffeeScript, reproduced exactly: getWords (switch) = 1, nested try/if/for/while = 9, nested lambda = 2 — plus compiler-grounded ternary cases, logical-operator sequences, existential/soak = 0, recursion, comprehension policies, and else if vs. nested-if-in-else. The tool also agrees with the canonical eslint-plugin-sonarjs reference on every snippet containing no CoffeeScript sugar.

Limitations

  • Direct recursion only — mutual/indirect recursion isn't detected (needs a whole-program call graph).
  • Parses whole-file with the installed CoffeeScript 2.x (+ the bare-super shim). The control-flow AST is stable across 2.x, so the score is unaffected.
  • Comprehension weighting is a documented judgment call (--comprehensions); the default (loop) is the most spec-literal.

API

const { analyzeSource, Analyzer, DEFAULT_OPTIONS } = require('coffeescript-cognitive-complexity');

analyzeSource(src, filename, options)
//   options: { comprehensions: 'loop'|'shorthand'|'off', superShim: true }
//   → { file, functions, fileTop, total, classes }  |  { file, error }
// Each function: { name, klass, line, complexity, increments:[{delta,line,reason,nesting}] }

License

MIT