Repository: macbre/analyze-css
Pull request #364 (2021-08-04 16:08:33)

Simplify imports and split the code
This reverts import related changes introduces in #363:

The following will still be used:

```js
const analyze = require('analyze-css');
```

coveralls: ## Pull Request Test Coverage Report for [Build 1098385870](https://coveralls.io/builds/41904452)

* **111** of **111**   **(100.0%)**  changed or added relevant lines in **2** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage remained the same at **100.0%**

---



|  Totals | [![Coverage Status](https://coveralls.io/builds/41904452/badge)](https://coveralls.io/builds/41904452) |
| :-- | --: |
| Change from base [Build 1098291598](https://coveralls.io/builds/41903620): |  0.0% |
| Covered Lines: | 533 |
| Relevant Lines: | 533 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/README.md b/README.md
index dc30965..a1adb1d 100644
--- a/README.md
+++ b/README.md
@@ -54,7 +54,7 @@ npm i --save analyze-css
 ```
 
 ```js
-const analyze = require('analyze-css').analyze;
+const analyze = require('analyze-css');
 
 (async() => {
   const results = await analyze('.foo {margin: 0 !important}');
diff --git a/bin/analyze-css.js b/bin/analyze-css.js
index 7fd5370..f7b99cc 100755
--- a/bin/analyze-css.js
+++ b/bin/analyze-css.js
@@ -9,7 +9,7 @@
 
 const { program } = require("commander");
 
-var analyzer = require("./../lib/index").analyze,
+var analyzer = require("./../lib/index"),
   debug = require("debug")("analyze-css:bin"),
   runner = require("./../lib/runner"),
   runnerOpts = {};
diff --git a/examples/example.js b/examples/example.js
index 77312a9..4dd7acc 100755
--- a/examples/example.js
+++ b/examples/example.js
@@ -1,5 +1,5 @@
 #!/usr/bin/env node
-const analyze = require('../').analyze;
+const analyze = require('../');
 
 (async() => {
   const results = await analyze('.foo {margin: 0 !important}');
diff --git a/lib/css-analyzer.d.ts b/lib/css-analyzer.d.ts
new file mode 100644
index 0000000..b4dd49a
--- /dev/null
+++ b/lib/css-analyzer.d.ts
@@ -0,0 +1,31 @@
+/**
+ * analyzer instance passed to the rules code
+ *
+ * See https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html
+ */
+
+import { MetricsNames, Metrics, Offenders } from "./helpers.d";
+
+declare class CSSAnalyzer {
+  public setMetric(
+    name: MetricsNames,
+    value: number | undefined /** = 0 */
+  ): void;
+  public incrMetric(
+    name: MetricsNames,
+    incr: number | undefined /** = 1 */
+  ): void;
+  public addOffender(
+    metricName: MetricsNames,
+    msg: string,
+    position: any | undefined
+  ): void;
+  public setCurrentPosition(position: any): void;
+  public on(ev: string, fn: any): void;
+  public analyze(css: string): boolean | Error;
+
+  public metrics: Metrics;
+  public offenders: Offenders;
+}
+
+export = CSSAnalyzer;
diff --git a/lib/css-analyzer.js b/lib/css-analyzer.js
new file mode 100644
index 0000000..1dadfaf
--- /dev/null
+++ b/lib/css-analyzer.js
@@ -0,0 +1,304 @@
+const analyze = require("."),
+  cssParser = require("css").parse,
+  debug = require("debug")("analyze-css:analyzer"),
+  fs = require("fs"),
+  slickParse = require("slick").parse;
+
+function error(msg, code) {
+  var err = new Error(msg);
+  err.code = code;
+
+  return err;
+}
+
+class CSSAnalyzer {
+  constructor(options) {
+    this.options = options;
+
+    this.metrics = {};
+    this.offenders = {};
+
+    // init events emitter
+    this.emitter = new (require("events").EventEmitter)();
+    this.emitter.setMaxListeners(200);
+  }
+
+  // emit given event
+  emit(/* eventName, arg1, arg2, ... */) {
+    //debug('Event %s emitted', arguments[0]);
+    this.emitter.emit.apply(this.emitter, arguments);
+  }
+
+  // bind to a given event
+  on(ev, fn) {
+    this.emitter.on(ev, fn);
+  }
+
+  setMetric(name, value) {
+    value = value || 0;
+
+    //debug('setMetric(%s) = %d', name, value);
+    this.metrics[name] = value;
+  }
+
+  // increements given metric by given number (default is one)
+  incrMetric(name, incr /* =1 */) {
+    var currVal = this.metrics[name] || 0;
+    incr = incr || 1;
+
+    //debug('incrMetric(%s) += %d', name, incr);
+    this.setMetric(name, currVal + incr);
+  }
+
+  addOffender(metricName, msg, position /* = undefined */) {
+    if (typeof this.offenders[metricName] === "undefined") {
+      this.offenders[metricName] = [];
+    }
+
+    this.offenders[metricName].push({
+      message: msg,
+      position: position || this.currentPosition,
+    });
+  }
+
+  setCurrentPosition(position) {
+    this.currentPosition = position;
+  }
+
+  initRules() {
+    var debug = require("debug")("analyze-css:rules"),
+      re = /\.js$/,
+      rules = [];
+
+    // load all rules
+    rules = fs
+      .readdirSync(fs.realpathSync(__dirname + "/../rules/"))
+      // filter out all non *.js files
+      .filter(function (file) {
+        return re.test(file);
+      })
+      // remove file extensions to get just names
+      .map(function (file) {
+        return file.replace(re, "");
+      });
+
+    debug("Rules to be loaded: %s", rules.join(", "));
+
+    rules.forEach(function (name) {
+      var rule = require("./../rules/" + name);
+      rule(this);
+
+      debug('"%s" loaded: %s', name, rule.description);
+    }, this);
+  }
+
+  fixCss(css) {
+    // properly handle ; in @import URLs
+    // see https://github.com/macbre/analyze-css/pull/322
+    // see https://github.com/reworkcss/css/issues/137
+    return css.replace(/@import url([^)]+["'])/, (match) => {
+      return match.replace(/;/g, "%3B");
+    });
+  }
+
+  parseCss(css) {
+    var debug = require("debug")("analyze-css:parser");
+    debug("Going to parse %s kB of CSS", (css.length / 1024).toFixed(2));
+
+    if (css.trim() === "") {
+      return error("Empty CSS was provided", analyze.EXIT_EMPTY_CSS);
+    }
+
+    css = this.fixCss(css);
+
+    this.tree = cssParser(css, {
+      // errors are listed in the parsingErrors property instead of being thrown (#84)
+      silent: true,
+    });
+
+    debug("CSS parsed");
+    return true;
+  }
+
+  parseRules(rules) {
+    const debug = require("debug")("analyze-css:parseRules");
+
+    rules.forEach(function (rule) {
+      debug("%j", rule);
+
+      // store the default current position
+      //
+      // it will be used when this.addOffender is called from within the rule
+      // it can be overridden by providing a "custom" position via a call to this.setCurrentPosition
+      this.setCurrentPosition(rule.position);
+
+      switch (rule.type) {
+        // {
+        //  "type":"media"
+        //  "media":"screen and (min-width: 1370px)",
+        //  "rules":[{"type":"rule","selectors":["#foo"],"declarations":[]}]
+        // }
+        case "media":
+          this.emit("media", rule.media, rule.rules);
+
+          // now run recursively to parse rules within the media query
+          /* istanbul ignore else */
+          if (rule.rules) {
+            this.parseRules(rule.rules);
+          }
+
+          this.emit("mediaEnd", rule.media, rule.rules);
+          break;
+
+        // {
+        //  "type":"rule",
+        //  "selectors":[".ui-header .ui-btn-up-a",".ui-header .ui-btn-hover-a"],
+        //  "declarations":[{"type":"declaration","property":"border","value":"0"},{"type":"declaration","property":"background","value":"none"}]
+        // }
+        case "rule":
+          if (!rule.selectors || !rule.declarations) {
+            return;
+          }
+
+          this.emit("rule", rule);
+
+          // analyze each selector and declaration
+          rule.selectors.forEach(function (selector) {
+            var parsedSelector,
+              expressions = [],
+              i,
+              len;
+
+            // "#features > div:first-child" will become two expressions:
+            //  {"combinator":" ","tag":"*","id":"features"}
+            //  {"combinator":">","tag":"div","pseudos":[{"key":"first-child","value":null}]}
+            parsedSelector = slickParse(selector)[0];
+
+            if (typeof parsedSelector === "undefined") {
+              var positionDump =
+                "Rule position start @ " +
+                rule.position.start.line +
+                ":" +
+                rule.position.start.column +
+                ", end @ " +
+                rule.position.end.line +
+                ":" +
+                rule.position.end.column;
+              throw error(
+                'Unable to parse "' + selector + '" selector. ' + positionDump,
+                analyze.EXIT_PARSING_FAILED
+              );
+            }
+
+            // convert object with keys to array with numeric index
+            for (i = 0, len = parsedSelector.length; i < len; i++) {
+              expressions.push(parsedSelector[i]);
+            }
+
+            this.emit("selector", rule, selector, expressions);
+
+            expressions.forEach(function (expression) {
+              this.emit("expression", selector, expression);
+            }, this);
+          }, this);
+
+          rule.declarations.forEach(function (declaration) {
+            this.setCurrentPosition(declaration.position);
+
+            switch (declaration.type) {
+              case "declaration":
+                this.emit(
+                  "declaration",
+                  rule,
+                  declaration.property,
+                  declaration.value
+                );
+                break;
+
+              case "comment":
+                this.emit("comment", declaration.comment);
+                break;
+            }
+          }, this);
+          break;
+
+        // {"type":"comment","comment":" Cached as static-css-r518-9b0f5ab4632defb55d67a1d672aa31bd120f4414 "}
+        case "comment":
+          this.emit("comment", rule.comment);
+          break;
+
+        // {"type":"font-face","declarations":[{"type":"declaration","property":"font-family","value":"myFont"...
+        case "font-face":
+          this.emit("font-face", rule);
+          break;
+
+        // {"type":"import","import":"url('/css/styles.css')"}
+        case "import":
+          // replace encoded semicolon back into ;
+          // https://github.com/macbre/analyze-css/pull/322
+          this.emit("import", rule.import.replace(/%3B/g, ";"));
+          break;
+      }
+    }, this);
+  }
+
+  run() {
+    var stylesheet = this.tree && this.tree.stylesheet,
+      rules = stylesheet && stylesheet.rules;
+
+    this.emit("stylesheet", stylesheet);
+
+    // check for parsing errors (#84)
+    stylesheet.parsingErrors.forEach(function (err) {
+      debug("error: %j", err);
+
+      var pos = {
+        line: err.line,
+        column: err.column,
+      };
+      this.setCurrentPosition({
+        start: pos,
+        end: pos,
+      });
+
+      this.emit("error", err);
+    }, this);
+
+    this.parseRules(rules);
+  }
+
+  analyze(css) {
+    var res,
+      then = Date.now();
+
+    this.metrics = {};
+    this.offenders = {};
+
+    // load and init all rules
+    this.initRules();
+
+    // parse CSS
+    res = this.parseCss(css);
+
+    if (res !== true) {
+      debug("parseCss() returned an error: " + res);
+      return res;
+    }
+
+    this.emit("css", css);
+
+    // now go through parsed CSS tree and emit events for rules
+    try {
+      this.run();
+    } catch (ex) {
+      return ex;
+    }
+
+    this.emit("report");
+
+    debug("Completed in %d ms", Date.now() - then);
+    return true;
+  }
+}
+
+module.exports = CSSAnalyzer;
diff --git a/lib/helpers.d.ts b/lib/helpers.d.ts
new file mode 100644
index 0000000..79d7e5b
--- /dev/null
+++ b/lib/helpers.d.ts
@@ -0,0 +1,48 @@
+declare type MetricsNames =
+  | "base64Length"
+  | "colors"
+  | "comments"
+  | "commentsLength"
+  | "complexSelectors"
+  | "declarations"
+  | "duplicatedProperties"
+  | "duplicatedSelectors"
+  | "emptyRules"
+  | "expressions"
+  | "importants"
+  | "imports"
+  | "length"
+  | "mediaQueries"
+  | "multiClassesSelectors"
+  | "notMinified"
+  | "oldIEFixes"
+  | "oldPropertyPrefixes"
+  | "parsingErrors"
+  | "propertyResets"
+  | "qualifiedSelectors"
+  | "redundantBodySelectors"
+  | "redundantChildNodesSelectors"
+  | "rules"
+  | "selectorLengthAvg"
+  | "selectors"
+  | "selectorsByAttribute"
+  | "selectorsByClass"
+  | "selectorsById"
+  | "selectorsByPseudo"
+  | "selectorsByTag"
+  | "specificityClassAvg"
+  | "specificityClassTotal"
+  | "specificityIdAvg"
+  | "specificityIdTotal"
+  | "specificityTagAvg"
+  | "specificityTagTotal";
+
+/**
+ * Encapsulates a set of metrics
+ */
+export type Metrics = { [metric in MetricsNames]: number };
+
+/**
+ * Encapsulates a set of offenders
+ */
+export type Offenders = { [metric in MetricsNames]: Array<Object> };
diff --git a/lib/index.d.ts b/lib/index.d.ts
index e723655..96f892a 100644
--- a/lib/index.d.ts
+++ b/lib/index.d.ts
@@ -4,54 +4,7 @@
  * While this package is not written in TypeScript, this file will allow your IDE to provide you with auto-completion.
  */
 
-declare type MetricsNames =
-  | "base64Length"
-  | "colors"
-  | "comments"
-  | "commentsLength"
-  | "complexSelectors"
-  | "declarations"
-  | "duplicatedProperties"
-  | "duplicatedSelectors"
-  | "emptyRules"
-  | "expressions"
-  | "importants"
-  | "imports"
-  | "length"
-  | "mediaQueries"
-  | "multiClassesSelectors"
-  | "notMinified"
-  | "oldIEFixes"
-  | "oldPropertyPrefixes"
-  | "parsingErrors"
-  | "propertyResets"
-  | "qualifiedSelectors"
-  | "redundantBodySelectors"
-  | "redundantChildNodesSelectors"
-  | "rules"
-  | "selectorLengthAvg"
-  | "selectors"
-  | "selectorsByAttribute"
-  | "selectorsByClass"
-  | "selectorsById"
-  | "selectorsByPseudo"
-  | "selectorsByTag"
-  | "specificityClassAvg"
-  | "specificityClassTotal"
-  | "specificityIdAvg"
-  | "specificityIdTotal"
-  | "specificityTagAvg"
-  | "specificityTagTotal";
-
-/**
- * Encapsulates a set of metrics
- */
-declare type Metrics = { [metric in MetricsNames]: number };
-
-/**
- * Encapsulates a set of offenders
- */
-declare type Offenders = { [metric in MetricsNames]: Array<Object> };
+import { Metrics, Offenders } from "./helpers.d";
 
 /**
  * Encapsulates results from analyze()
@@ -62,30 +15,9 @@ declare interface Results {
   offenders: Offenders;
 }
 
-/**
- * analyzer instance passed to the rules code
- *
- * See https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html
- */
-export class CSSAnalyzer {
-  public setMetric(
-    name: MetricsNames,
-    value: number | undefined /** = 0 */
-  ): void;
-  public incrMetric(
-    name: MetricsNames,
-    incr: number | undefined /** = 1 */
-  ): void;
-  public addOffender(
-    metricName: MetricsNames,
-    msg: string,
-    position: any | undefined
-  ): void;
-  public setCurrentPosition(position: any): void;
-  public on(ev: string, fn: any): void;
-}
-
 /**
  * The main entry-point to analyze a given css
  */
-export function analyze(css: string, options?: object): Promise<Results>;
+declare function analyze(css: string, options?: object): Promise<Results>;
+
+export = analyze;
diff --git a/lib/index.js b/lib/index.js
index 023eceb..dbf547d 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -3,12 +3,9 @@
  */
 "use strict";
 
-var cssParser = require("css").parse,
-  debug = require("debug")("analyze-css"),
-  fs = require("fs"),
+const debug = require("debug")("analyze-css"),
   path = require("path"),
   preprocessors = new (require("./preprocessors"))(),
-  slickParse = require("slick").parse,
   VERSION = require("./../package").version;
 
 function error(msg, code) {
@@ -51,6 +48,7 @@ function analyze(css, options) {
       debug("Preprocessing completed");
     }
 
+    const CSSAnalyzer = require("./css-analyzer");
     const instance = new CSSAnalyzer(options);
     const res = instance.analyze(css);
 
@@ -91,297 +89,4 @@ analyze.EXIT_CSS_PASSED_IS_NOT_STRING = 253;
 analyze.EXIT_URL_LOADING_FAILED = 254;
 analyze.EXIT_FILE_LOADING_FAILED = 255;
 
-class CSSAnalyzer {
-  constructor(options) {
-    this.options = options;
-
-    this.metrics = {};
-    this.offenders = {};
-
-    // init events emitter
-    this.emitter = new (require("events").EventEmitter)();
-    this.emitter.setMaxListeners(200);
-  }
-
-  // emit given event
-  emit(/* eventName, arg1, arg2, ... */) {
-    //debug('Event %s emitted', arguments[0]);
-    this.emitter.emit.apply(this.emitter, arguments);
-  }
-
-  // bind to a given event
-  on(ev, fn) {
-    this.emitter.on(ev, fn);
-  }
-
-  setMetric(name, value) {
-    value = value || 0;
-
-    //debug('setMetric(%s) = %d', name, value);
-    this.metrics[name] = value;
-  }
-
-  // increements given metric by given number (default is one)
-  incrMetric(name, incr /* =1 */) {
-    var currVal = this.metrics[name] || 0;
-    incr = incr || 1;
-
-    //debug('incrMetric(%s) += %d', name, incr);
-    this.setMetric(name, currVal + incr);
-  }
-
-  addOffender(metricName, msg, position /* = undefined */) {
-    if (typeof this.offenders[metricName] === "undefined") {
-      this.offenders[metricName] = [];
-    }
-
-    this.offenders[metricName].push({
-      message: msg,
-      position: position || this.currentPosition,
-    });
-  }
-
-  setCurrentPosition(position) {
-    this.currentPosition = position;
-  }
-
-  initRules() {
-    var debug = require("debug")("analyze-css:rules"),
-      re = /\.js$/,
-      rules = [];
-
-    // load all rules
-    rules = fs
-      .readdirSync(fs.realpathSync(__dirname + "/../rules/"))
-      // filter out all non *.js files
-      .filter(function (file) {
-        return re.test(file);
-      })
-      // remove file extensions to get just names
-      .map(function (file) {
-        return file.replace(re, "");
-      });
-
-    debug("Rules to be loaded: %s", rules.join(", "));
-
-    rules.forEach(function (name) {
-      var rule = require("./../rules/" + name);
-      rule(this);
-
-      debug('"%s" loaded: %s', name, rule.description);
-    }, this);
-  }
-
-  fixCss(css) {
-    // properly handle ; in @import URLs
-    // see https://github.com/macbre/analyze-css/pull/322
-    // see https://github.com/reworkcss/css/issues/137
-    return css.replace(/@import url([^)]+["'])/, (match) => {
-      return match.replace(/;/g, "%3B");
-    });
-  }
-
-  parseCss(css) {
-    var debug = require("debug")("analyze-css:parser");
-    debug("Going to parse %s kB of CSS", (css.length / 1024).toFixed(2));
-
-    if (css.trim() === "") {
-      return error("Empty CSS was provided", analyze.EXIT_EMPTY_CSS);
-    }
-
-    css = this.fixCss(css);
-
-    this.tree = cssParser(css, {
-      // errors are listed in the parsingErrors property instead of being thrown (#84)
-      silent: true,
-    });
-
-    debug("CSS parsed");
-    return true;
-  }
-
-  parseRules(rules) {
-    const debug = require("debug")("analyze-css:parseRules");
-
-    rules.forEach(function (rule) {
-      debug("%j", rule);
-
-      // store the default current position
-      //
-      // it will be used when this.addOffender is called from within the rule
-      // it can be overridden by providing a "custom" position via a call to this.setCurrentPosition
-      this.setCurrentPosition(rule.position);
-
-      switch (rule.type) {
-        // {
-        //  "type":"media"
-        //  "media":"screen and (min-width: 1370px)",
-        //  "rules":[{"type":"rule","selectors":["#foo"],"declarations":[]}]
-        // }
-        case "media":
-          this.emit("media", rule.media, rule.rules);
-
-          // now run recursively to parse rules within the media query
-          /* istanbul ignore else */
-          if (rule.rules) {
-            this.parseRules(rule.rules);
-          }
-
-          this.emit("mediaEnd", rule.media, rule.rules);
-          break;
-
-        // {
-        //  "type":"rule",
-        //  "selectors":[".ui-header .ui-btn-up-a",".ui-header .ui-btn-hover-a"],
-        //  "declarations":[{"type":"declaration","property":"border","value":"0"},{"type":"declaration","property":"background","value":"none"}]
-        // }
-        case "rule":
-          if (!rule.selectors || !rule.declarations) {
-            return;
-          }
-
-          this.emit("rule", rule);
-
-          // analyze each selector and declaration
-          rule.selectors.forEach(function (selector) {
-            var parsedSelector,
-              expressions = [],
-              i,
-              len;
-
-            // "#features > div:first-child" will become two expressions:
-            //  {"combinator":" ","tag":"*","id":"features"}
-            //  {"combinator":">","tag":"div","pseudos":[{"key":"first-child","value":null}]}
-            parsedSelector = slickParse(selector)[0];
-
-            if (typeof parsedSelector === "undefined") {
-              var positionDump =
-                "Rule position start @ " +
-                rule.position.start.line +
-                ":" +
-                rule.position.start.column +
-                ", end @ " +
-                rule.position.end.line +
-                ":" +
-                rule.position.end.column;
-              throw error(
-                'Unable to parse "' + selector + '" selector. ' + positionDump,
-                analyze.EXIT_PARSING_FAILED
-              );
-            }
-
-            // convert object with keys to array with numeric index
-            for (i = 0, len = parsedSelector.length; i < len; i++) {
-              expressions.push(parsedSelector[i]);
-            }
-
-            this.emit("selector", rule, selector, expressions);
-
-            expressions.forEach(function (expression) {
-              this.emit("expression", selector, expression);
-            }, this);
-          }, this);
-
-          rule.declarations.forEach(function (declaration) {
-            this.setCurrentPosition(declaration.position);
-
-            switch (declaration.type) {
-              case "declaration":
-                this.emit(
-                  "declaration",
-                  rule,
-                  declaration.property,
-                  declaration.value
-                );
-                break;
-
-              case "comment":
-                this.emit("comment", declaration.comment);
-                break;
-            }
-          }, this);
-          break;
-
-        // {"type":"comment","comment":" Cached as static-css-r518-9b0f5ab4632defb55d67a1d672aa31bd120f4414 "}
-        case "comment":
-          this.emit("comment", rule.comment);
-          break;
-
-        // {"type":"font-face","declarations":[{"type":"declaration","property":"font-family","value":"myFont"...
-        case "font-face":
-          this.emit("font-face", rule);
-          break;
-
-        // {"type":"import","import":"url('/css/styles.css')"}
-        case "import":
-          // replace encoded semicolon back into ;
-          // https://github.com/macbre/analyze-css/pull/322
-          this.emit("import", rule.import.replace(/%3B/g, ";"));
-          break;
-      }
-    }, this);
-  }
-
-  run() {
-    var stylesheet = this.tree && this.tree.stylesheet,
-      rules = stylesheet && stylesheet.rules;
-
-    this.emit("stylesheet", stylesheet);
-
-    // check for parsing errors (#84)
-    stylesheet.parsingErrors.forEach(function (err) {
-      debug("error: %j", err);
-
-      var pos = {
-        line: err.line,
-        column: err.column,
-      };
-      this.setCurrentPosition({
-        start: pos,
-        end: pos,
-      });
-
-      this.emit("error", err);
-    }, this);
-
-    this.parseRules(rules);
-  }
-
-  analyze(css) {
-    var res,
-      then = Date.now();
-
-    this.metrics = {};
-    this.offenders = {};
-
-    // load and init all rules
-    this.initRules();
-
-    // parse CSS
-    res = this.parseCss(css);
-
-    if (res !== true) {
-      debug("parseCss() returned an error: " + res);
-      return res;
-    }
-
-    this.emit("css", css);
-
-    // now go through parsed CSS tree and emit events for rules
-    try {
-      this.run();
-    } catch (ex) {
-      return ex;
-    }
-
-    this.emit("report");
-
-    debug("Completed in %d ms", Date.now() - then);
-    return true;
-  }
-}
-
-module.exports = {
-  analyze,
-  CSSAnalyzer,
-};
+module.exports = analyze;
diff --git a/lib/runner.js b/lib/runner.js
index 42cb120..e770ca9 100644
--- a/lib/runner.js
+++ b/lib/runner.js
@@ -9,7 +9,7 @@ var cli = require("cli"),
   debug = require("debug")("analyze-css:runner"),
   fs = require("fs"),
   resolve = require("path").resolve,
-  analyzer = require("./index").analyze,
+  analyzer = require("./index"),
   preprocessors = new (require("./preprocessors"))();
 
 /**
diff --git a/rules/base64.js b/rules/base64.js
index 3d6075e..ad368fd 100644
--- a/rules/base64.js
+++ b/rules/base64.js
@@ -3,6 +3,9 @@
 var format = require("util").format,
   MAX_LENGTH = 4 * 1024;
 
+/**
+ * @param { import("../lib/index").CSSAnalyzer } analyzer
+ */
 function rule(analyzer) {
   // @see http://stackoverflow.com/a/11335500
   var re = /data:.+\/(.+);base64,(.*)\)/;
diff --git a/rules/comments.js b/rules/comments.js
index a1e2d90..c4a4a63 100644
--- a/rules/comments.js
+++ b/rules/comments.js
@@ -4,7 +4,7 @@ const format = require("util").format,
   MAX_LENGTH = 256;
 
 /**
- * @param { import("../lib/index").CSSAnalyzer } analyzer
+ * @param { import("../lib/css-analyzer").CSSAnalyzer } analyzer
  */
 function rule(analyzer) {
   analyzer.setMetric("comments");


Test patch:
diff --git a/test/errors.test.js b/test/errors.test.js
index 9f99822..381144d 100644
--- a/test/errors.test.js
+++ b/test/errors.test.js
@@ -1,6 +1,6 @@
 const { describe, it } = require("@jest/globals");
 
-var analyzer = require('../').analyze,
+var analyzer = require('../'),
 	assert = require('assert'),
 	tests;
 
diff --git a/test/fixes/280-url-with-semicolons.test.js b/test/fixes/280-url-with-semicolons.test.js
index 3f0a6ee..87f4267 100644
--- a/test/fixes/280-url-with-semicolons.test.js
+++ b/test/fixes/280-url-with-semicolons.test.js
@@ -3,7 +3,7 @@ const assert = require('assert');
 
 describe('@import rule with url containing semicolon', () => {
 	it('is properly parsed', () => {
-        const analyzer = require('../..').analyze,
+        const analyzer = require('../..'),
             css = `
 @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;800&display=swap');
 
diff --git a/test/opts.test.js b/test/opts.test.js
index 46500b1..abee8f8 100644
--- a/test/opts.test.js
+++ b/test/opts.test.js
@@ -1,6 +1,6 @@
 const { describe, it } = require("@jest/globals");
 
-var analyzer = require('../').analyze,
+var analyzer = require('../'),
 	assert = require('assert'),
 	css = '.foo { color: white }';
 
diff --git a/test/rules.test.js b/test/rules.test.js
index 7740eb6..6921a99 100644
--- a/test/rules.test.js
+++ b/test/rules.test.js
@@ -1,6 +1,6 @@
 const { describe, it } = require("@jest/globals");
 
-const analyzer = require('../').analyze,
+const analyzer = require('../'),
 	assert = require('assert'),
 	glob = require('glob');
 
diff --git a/test/sass.test.js b/test/sass.test.js
index d1d8589..8bfedd3 100644
--- a/test/sass.test.js
+++ b/test/sass.test.js
@@ -1,6 +1,6 @@
 const { describe, it } = require("@jest/globals");
 
-var analyzer = require('../').analyze,
+var analyzer = require('../'),
 	fs = require('fs'),
 	isSassInstalled = true,
 	assert = require('assert'),