/** * @author Toru Nagashima * See LICENSE file in root directory for full license. */ "use strict" const utils = require("./utils") const COMMENT_DIRECTIVE = /^\s*(eslint-(?:en|dis)able(?:(?:-next)?-line)?)\s*(?:(\S|\S[\s\S]*\S)\s*)?$/u const DELIMITER = /[\s,]+/gu const pool = new WeakMap() module.exports = class DisabledArea { /** * Get singleton instance for the given source code. * * @param {eslint.SourceCode} sourceCode - The source code to get. * @returns {DisabledArea} The singleton object for the source code. */ static get(sourceCode) { let retv = pool.get(sourceCode.ast) if (retv == null) { retv = new DisabledArea() retv._scan(sourceCode) pool.set(sourceCode.ast, retv) } return retv } /** * Constructor. */ constructor() { this.areas = [] this.duplicateDisableDirectives = [] this.unusedEnableDirectives = [] this.numberOfRelatedDisableDirectives = new Map() } /** * Make disabled area. * * @param {Token} comment - The comment token to disable. * @param {object} location - The start location to disable. * @param {string[]|null} ruleIds - The ruleId names to disable. * @param {string} kind - The kind of disable-comments. * @returns {void} * @private */ _disable(comment, location, ruleIds, kind) { if (ruleIds) { for (const ruleId of ruleIds) { if (this._getArea(ruleId, location) != null) { this.duplicateDisableDirectives.push({ comment, ruleId }) } this.areas.push({ comment, ruleId, kind, start: location, end: null, }) } } else { if (this._getArea(null, location) != null) { this.duplicateDisableDirectives.push({ comment, ruleId: null }) } this.areas.push({ comment, ruleId: null, kind, start: location, end: null, }) } } /** * Close disabled area. * * @param {Token} comment - The comment token to enable. * @param {object} location - The start location to enable. * @param {string[]|null} ruleIds - The ruleId names to enable. * @param {string} kind - The kind of disable-comments. * @returns {void} * @private */ _enable(comment, location, ruleIds, kind) { const relatedDisableDirectives = new Set() if (ruleIds) { for (const ruleId of ruleIds) { let used = false for (let i = this.areas.length - 1; i >= 0; --i) { const area = this.areas[i] if ( area.end === null && area.kind === kind && area.ruleId === ruleId ) { relatedDisableDirectives.add(area.comment) area.end = location used = true } } if (!used) { this.unusedEnableDirectives.push({ comment, ruleId }) } } } else { let used = false for (let i = this.areas.length - 1; i >= 0; --i) { const area = this.areas[i] if (area.end === null && area.kind === kind) { relatedDisableDirectives.add(area.comment) area.end = location used = true } } if (!used) { this.unusedEnableDirectives.push({ comment, ruleId: null }) } } this.numberOfRelatedDisableDirectives.set( comment, relatedDisableDirectives.size ) } /** * Gets the area of the given ruleId and location. * * @param {string|null} ruleId - The ruleId name to get. * @param {object} location - The location to get. * @returns {object|null} The area of the given ruleId and location. * @private */ _getArea(ruleId, location) { for (let i = this.areas.length - 1; i >= 0; --i) { const area = this.areas[i] if ( (area.ruleId === null || area.ruleId === ruleId) && utils.lte(area.start, location) && (area.end === null || utils.lte(location, area.end)) ) { return area } } return null } /** * Scan the souce code and setup disabled area list. * * @param {eslint.SourceCode} sourceCode - The source code to scan. * @returns {void} * @private */ _scan(sourceCode) { for (const comment of sourceCode.getAllComments()) { const m = COMMENT_DIRECTIVE.exec(comment.value) if (m == null) { continue } const kind = m[1] const ruleIds = m[2] ? m[2].split(DELIMITER) : null if (comment.type === "Block" && kind === "eslint-disable") { this._disable(comment, comment.loc.start, ruleIds, "block") } else if (comment.type === "Block" && kind === "eslint-enable") { this._enable(comment, comment.loc.start, ruleIds, "block") } else if ( comment.type === "Line" && kind === "eslint-disable-line" ) { const line = comment.loc.start.line const start = { line, column: 0 } const end = { line: line + 1, column: -1 } this._disable(comment, start, ruleIds, "line") this._enable(comment, end, ruleIds, "line") } else if ( comment.type === "Line" && kind === "eslint-disable-next-line" ) { const line = comment.loc.start.line const start = { line: line + 1, column: 0 } const end = { line: line + 2, column: -1 } this._disable(comment, start, ruleIds, "line") this._enable(comment, end, ruleIds, "line") } } } }