From e5bbcf148874be07d5667f34ed395faaf8c72972 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 30 Aug 2024 06:28:28 -0400 Subject: [PATCH] feat: Add Directive and DirectiveType (#112) Co-authored-by: Milos Djermanovic --- packages/core/src/types.ts | 13 ++++- packages/plugin-kit/README.md | 50 ++++++++++++++++++ packages/plugin-kit/src/index.js | 1 + packages/plugin-kit/src/source-code.js | 52 +++++++++++++++++++ packages/plugin-kit/tests/source-code.test.js | 22 ++++++++ 5 files changed, 136 insertions(+), 2 deletions(-) diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index b2c22a8..45a1116 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -419,6 +419,15 @@ export interface CallTraversalStep { export type TraversalStep = VisitTraversalStep | CallTraversalStep; +/** + * The type of disable directive. This determines how ESLint will disable rules. + */ +export type DirectiveType = + | "disable" + | "enable" + | "disable-line" + | "disable-next-line"; + /** * Represents a disable directive. */ @@ -426,12 +435,12 @@ export interface Directive { /** * The type of directive. */ - type: "disable" | "enable" | "disable-line" | "disable-next-line"; + type: DirectiveType; /** * The node of the directive. May be in the AST or a comment/token. */ - node: object; + node: unknown; /** * The value of the directive. diff --git a/packages/plugin-kit/README.md b/packages/plugin-kit/README.md index 8978291..6ebbbf7 100644 --- a/packages/plugin-kit/README.md +++ b/packages/plugin-kit/README.md @@ -30,6 +30,7 @@ This package exports the following utilities: - `ConfigCommentParser` - used to parse ESLint configuration comments (i.e., `/* eslint-disable rule */`) - `VisitNodeStep` and `CallMethodStep` - used to help implement `SourceCode#traverse()` +- `Directive` - used to help implement `SourceCode#getDisableDirectives()` - `TextSourceCodeBase` - base class to help implement the `SourceCode` interface ### `ConfigCommentParser` @@ -151,6 +152,55 @@ class MySourceCode { } ``` +### `Directive` + +The `Directive` class represents a disable directive in the source code and implements the `Directive` interface from `@eslint/core`. You can tell ESLint about disable directives using the `SourceCode#getDisableDirectives()` method, where part of the return value is an array of `Directive` objects. Here's an example: + +```js +import { Directive, ConfigCommentParser } from "@eslint/plugin-kit"; + +class MySourceCode { + getDisableDirectives() { + const directives = []; + const problems = []; + const commentParser = new ConfigCommentParser(); + + // read in the inline config nodes to check each one + this.getInlineConfigNodes().forEach(comment => { + // Step 1: Parse the directive + const { label, value, justification } = + commentParser.parseDirective(comment.value); + + // Step 2: Extract the directive value and create the `Directive` object + switch (label) { + case "eslint-disable": + case "eslint-enable": + case "eslint-disable-next-line": + case "eslint-disable-line": { + const directiveType = label.slice("eslint-".length); + + directives.push( + new Directive({ + type: directiveType, + node: comment, + value, + justification, + }), + ); + } + + // ignore any comments that don't begin with known labels + } + }); + + return { + directives, + problems, + }; + } +} +``` + ### `TextSourceCodeBase` The `TextSourceCodeBase` class is intended to be a base class that has several of the common members found in `SourceCode` objects already implemented. Those members are: diff --git a/packages/plugin-kit/src/index.js b/packages/plugin-kit/src/index.js index 908fa32..26302be 100644 --- a/packages/plugin-kit/src/index.js +++ b/packages/plugin-kit/src/index.js @@ -8,4 +8,5 @@ export { CallMethodStep, VisitNodeStep, TextSourceCodeBase, + Directive, } from "./source-code.js"; diff --git a/packages/plugin-kit/src/source-code.js b/packages/plugin-kit/src/source-code.js index a198534..13854cc 100644 --- a/packages/plugin-kit/src/source-code.js +++ b/packages/plugin-kit/src/source-code.js @@ -16,6 +16,8 @@ /** @typedef {import("@eslint/core").SourceLocation} SourceLocation */ /** @typedef {import("@eslint/core").SourceLocationWithOffset} SourceLocationWithOffset */ /** @typedef {import("@eslint/core").SourceRange} SourceRange */ +/** @typedef {import("@eslint/core").Directive} IDirective */ +/** @typedef {import("@eslint/core").DirectiveType} DirectiveType */ //----------------------------------------------------------------------------- // Helpers @@ -158,6 +160,56 @@ export class CallMethodStep { } } +/** + * A class to represent a directive comment. + * @implements {IDirective} + */ +export class Directive { + /** + * The type of directive. + * @type {DirectiveType} + * @readonly + */ + type; + + /** + * The node representing the directive. + * @type {unknown} + * @readonly + */ + node; + + /** + * Everything after the "eslint-disable" portion of the directive, + * but before the "--" that indicates the justification. + * @type {string} + * @readonly + */ + value; + + /** + * The justification for the directive. + * @type {string} + * @readonly + */ + justification; + + /** + * Creates a new instance. + * @param {Object} options The options for the directive. + * @param {"disable"|"enable"|"disable-next-line"|"disable-line"} options.type The type of directive. + * @param {unknown} options.node The node representing the directive. + * @param {string} options.value The value of the directive. + * @param {string} options.justification The justification for the directive. + */ + constructor({ type, node, value, justification }) { + this.type = type; + this.node = node; + this.value = value; + this.justification = justification; + } +} + /** * Source Code Base Object * @implements {TextSourceCode} diff --git a/packages/plugin-kit/tests/source-code.test.js b/packages/plugin-kit/tests/source-code.test.js index 369f5e3..dcb9084 100644 --- a/packages/plugin-kit/tests/source-code.test.js +++ b/packages/plugin-kit/tests/source-code.test.js @@ -11,6 +11,7 @@ import assert from "node:assert"; import { CallMethodStep, VisitNodeStep, + Directive, TextSourceCodeBase, } from "../src/source-code.js"; @@ -63,6 +64,27 @@ describe("source-code", () => { }); }); + describe("Directive", () => { + it("should create a new instance", () => { + const type = "disable"; + const node = { foo: "bar" }; + const value = "baz"; + const justification = "qux"; + + const directive = new Directive({ + type, + node, + value, + justification, + }); + + assert.strictEqual(directive.type, type); + assert.strictEqual(directive.node, node); + assert.strictEqual(directive.value, value); + assert.strictEqual(directive.justification, justification); + }); + }); + describe("TextSourceCodeBase", () => { describe("new TextSourceCodeBase", () => { it("should create a new instance", () => {