From 13383af339798d4e6ecb3e8a35283f100146cfcb Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sun, 29 Oct 2023 01:12:16 +0700 Subject: [PATCH] Require Node.js 18 --- index.d.ts | 31 +++------- index.js | 150 ++++++++++++++++++++++++------------------------ index.test-d.ts | 62 ++++++++++---------- package.json | 36 +++++++----- test/test.js | 53 +++++++++++------ 5 files changed, 170 insertions(+), 162 deletions(-) diff --git a/index.d.ts b/index.d.ts index e74cb51..bf0ba03 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,7 +1,5 @@ import type {ESLint, Linter} from 'eslint'; -export = formatterPretty; - /** Pretty formatter for [ESLint](https://eslint.org). @@ -9,27 +7,14 @@ Pretty formatter for [ESLint](https://eslint.org). @param data - Extended information related to the analysis results. @returns The formatted output. */ -declare function formatterPretty( - results: readonly formatterPretty.LintResult[], - data?: ESLint.LintResultData +export default function eslintFormatterPretty( + results: LintResult[], + data?: LintResultData ): string; -declare namespace formatterPretty { - interface LintResult { - readonly filePath: string; - readonly errorCount: number; - readonly warningCount: number; - readonly messages: readonly LintMessage[]; - } - - type Severity = Linter.Severity | 'warning' | 'error'; +export type LintResult = ESLint.LintResult; +export type LintResultData = ESLint.LintResultData; +export type Severity = Linter.Severity; +export type LintMessage = Linter.LintMessage; - interface LintMessage { - readonly severity: Severity; - readonly fatal?: boolean; - readonly line?: number; - readonly column?: number; - readonly message: string; - readonly ruleId?: string | null; - } -} +export {Linter} from 'eslint'; diff --git a/index.js b/index.js index a8da1e4..3b9df7e 100644 --- a/index.js +++ b/index.js @@ -1,14 +1,14 @@ -'use strict'; -const path = require('path'); -const chalk = require('chalk'); -const logSymbols = require('log-symbols'); -const plur = require('plur'); -const stringWidth = require('string-width'); -const ansiEscapes = require('ansi-escapes'); -const {supportsHyperlink} = require('supports-hyperlinks'); -const getRuleDocs = require('eslint-rule-docs'); - -module.exports = (results, data) => { +import process from 'node:process'; +import path from 'node:path'; +import chalk from 'chalk'; +import logSymbols from 'log-symbols'; +import plur from 'plur'; +import stringWidth from 'string-width'; +import ansiEscapes from 'ansi-escapes'; +import {supportsHyperlink} from 'supports-hyperlinks'; +import getRuleDocs from 'eslint-rule-docs'; + +export default function eslintFormatterPretty(results, data) { const lines = []; let errorCount = 0; let warningCount = 0; @@ -17,7 +17,7 @@ module.exports = (results, data) => { let maxMessageWidth = 0; let showLineNumbers = false; - results + for (const result of results .sort((a, b) => { if (a.errorCount === b.errorCount) { return b.warningCount - a.warningCount; @@ -32,76 +32,74 @@ module.exports = (results, data) => { } return b.errorCount - a.errorCount; - }) - .forEach(result => { - const {messages, filePath} = result; + })) { + const {messages, filePath} = result; - if (messages.length === 0) { - return; - } + if (messages.length === 0) { + continue; + } - errorCount += result.errorCount; - warningCount += result.warningCount; + errorCount += result.errorCount; + warningCount += result.warningCount; - if (lines.length !== 0) { - lines.push({type: 'separator'}); - } - - const firstErrorOrWarning = messages.find(({severity}) => severity === 2) || messages[0]; + if (lines.length > 0) { + lines.push({type: 'separator'}); + } - lines.push({ - type: 'header', - filePath, - relativeFilePath: path.relative('.', filePath), - firstLineCol: firstErrorOrWarning.line + ':' + firstErrorOrWarning.column - }); + const firstErrorOrWarning = messages.find(({severity}) => severity === 2) ?? messages[0]; - messages - .sort((a, b) => { - if (a.fatal === b.fatal && a.severity === b.severity) { - if (a.line === b.line) { - return a.column < b.column ? -1 : 1; - } + lines.push({ + type: 'header', + filePath, + relativeFilePath: path.relative('.', filePath), + firstLineCol: firstErrorOrWarning.line + ':' + firstErrorOrWarning.column, + }); - return a.line < b.line ? -1 : 1; + for (const x of messages + .sort((a, b) => { + if (a.fatal === b.fatal && a.severity === b.severity) { + if (a.line === b.line) { + return a.column < b.column ? -1 : 1; } - if ((a.fatal || a.severity === 2) && (!b.fatal || b.severity !== 2)) { - return 1; - } + return a.line < b.line ? -1 : 1; + } - return -1; - }) - .forEach(x => { - let {message} = x; - - // Stylize inline code blocks - message = message.replace(/\B`(.*?)`\B|\B'(.*?)'\B/g, (m, p1, p2) => chalk.bold(p1 || p2)); - - const line = String(x.line || 0); - const column = String(x.column || 0); - const lineWidth = stringWidth(line); - const columnWidth = stringWidth(column); - const messageWidth = stringWidth(message); - - maxLineWidth = Math.max(lineWidth, maxLineWidth); - maxColumnWidth = Math.max(columnWidth, maxColumnWidth); - maxMessageWidth = Math.max(messageWidth, maxMessageWidth); - showLineNumbers = showLineNumbers || x.line || x.column; - - lines.push({ - type: 'message', - severity: (x.fatal || x.severity === 2 || x.severity === 'error') ? 'error' : 'warning', - line, - lineWidth, - column, - columnWidth, - message, - messageWidth, - ruleId: x.ruleId || '' - }); - }); - }); + if ((a.fatal || a.severity === 2) && (!b.fatal || b.severity !== 2)) { + return 1; + } + + return -1; + })) { + let {message} = x; + + // Stylize inline code blocks + message = message.replaceAll(/\B`(.*?)`\B|\B'(.*?)'\B/g, (m, p1, p2) => chalk.bold(p1 ?? p2)); + + const line = String(x.line ?? 0); + const column = String(x.column ?? 0); + const lineWidth = stringWidth(line); + const columnWidth = stringWidth(column); + const messageWidth = stringWidth(message); + + maxLineWidth = Math.max(lineWidth, maxLineWidth); + maxColumnWidth = Math.max(columnWidth, maxColumnWidth); + maxMessageWidth = Math.max(messageWidth, maxMessageWidth); + showLineNumbers = showLineNumbers || x.line || x.column; + + lines.push({ + type: 'message', + severity: (x.fatal || x.severity === 2 || x.severity === 'error') ? 'error' : 'warning', + line, + lineWidth, + column, + columnWidth, + message, + messageWidth, + ruleId: x.ruleId ?? '', + }); + } + } let output = '\n'; @@ -135,8 +133,8 @@ module.exports = (results, data) => { x.severity === 'warning' ? logSymbols.warning : logSymbols.error, ' '.repeat(maxLineWidth - x.lineWidth) + chalk.dim(x.line + chalk.gray(':') + x.column), ' '.repeat(maxColumnWidth - x.columnWidth) + x.message, - ' '.repeat(maxMessageWidth - x.messageWidth) + - (ruleUrl && supportsHyperlink(process.stdout) ? ansiEscapes.link(chalk.dim(x.ruleId), ruleUrl) : chalk.dim(x.ruleId)) + ' '.repeat(maxMessageWidth - x.messageWidth) + + (ruleUrl && supportsHyperlink(process.stdout) ? ansiEscapes.link(chalk.dim(x.ruleId), ruleUrl) : chalk.dim(x.ruleId)), ]; if (!showLineNumbers) { @@ -158,4 +156,4 @@ module.exports = (results, data) => { } return (errorCount + warningCount) > 0 ? output : ''; -}; +} diff --git a/index.test-d.ts b/index.test-d.ts index 898013d..da76186 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -1,50 +1,50 @@ import type {ESLint} from 'eslint'; import {expectType} from 'tsd'; -import formatterPretty = require('.'); - -// Test type exports -type LintResult = formatterPretty.LintResult; -type LintMessage = formatterPretty.LintMessage; -type Severity = formatterPretty.Severity; +import eslintFormatterPretty, { + type LintResult, + type LintMessage, + type Severity, +} from './index.js'; // Test LintResult interface members declare const lintResult: LintResult; expectType(lintResult.filePath); expectType(lintResult.errorCount); expectType(lintResult.warningCount); -expectType(lintResult.messages); +expectType(lintResult.messages); // Test LintMessage interface members const lintMessage = lintResult.messages[0]; expectType(lintMessage.severity); expectType(lintMessage.message); -expectType(lintMessage.fatal); -expectType(lintMessage.line); -expectType(lintMessage.column); -expectType(lintMessage.ruleId); +expectType(lintMessage.fatal); +expectType(lintMessage.line); +expectType(lintMessage.column); +expectType(lintMessage.ruleId); // eslint-disable-line @typescript-eslint/ban-types // Test formatterPretty() declare const lintResults: LintResult[]; declare const eslintLintResults: ESLint.LintResult[]; declare const lintResultData: ESLint.LintResultData; -expectType(formatterPretty(lintResults)); -expectType(formatterPretty(eslintLintResults)); -expectType(formatterPretty(eslintLintResults, lintResultData)); - -interface PartialLintResult { - filePath: string; - errorCount: number; - warningCount: number; - messages: Array<{ - fileName: string; - message: string; - severity: 'error' | 'warning'; - line?: number; - column?: number; - }>; -} - -declare const partialLintResults: PartialLintResult[]; - -expectType(formatterPretty(partialLintResults)); +expectType(eslintFormatterPretty(lintResults)); +expectType(eslintFormatterPretty(eslintLintResults)); +expectType(eslintFormatterPretty(eslintLintResults, lintResultData)); + +// FIXME +// type PartialLintResult = { +// filePath: string; +// errorCount: number; +// warningCount: number; +// messages: Array<{ +// fileName: string; +// message: string; +// severity: 0 | 1 | 2; +// line?: number; +// column?: number; +// }>; +// }; + +// declare const partialLintResults: PartialLintResult[]; + +// expectType(eslintFormatterPretty(partialLintResults)); diff --git a/package.json b/package.json index 1b018e7..8190536 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,13 @@ "email": "sindresorhus@gmail.com", "url": "https://sindresorhus.com" }, + "type": "module", + "exports": { + "types": "./index.d.ts", + "default": "./index.js" + }, "engines": { - "node": ">=14.16" + "node": ">=18" }, "scripts": { "test": "xo && ava && tsd" @@ -29,23 +34,28 @@ "validate" ], "dependencies": { - "@types/eslint": "^8.0.0", - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", + "@types/eslint": "^8.44.6", + "ansi-escapes": "^6.2.0", + "chalk": "^5.3.0", "eslint-rule-docs": "^1.1.235", - "log-symbols": "^4.0.0", - "plur": "^4.0.0", - "string-width": "^4.2.0", - "supports-hyperlinks": "^2.0.0" + "log-symbols": "^6.0.0", + "plur": "^5.1.0", + "string-width": "^7.0.0", + "supports-hyperlinks": "^3.0.0" }, "devDependencies": { - "ava": "^2.4.0", - "strip-ansi": "^6.0.0", - "tsd": "^0.17.0", - "typescript": "^4.3.2", - "xo": "^0.32.0" + "ava": "^5.3.1", + "strip-ansi": "^7.1.0", + "tsd": "^0.29.0", + "typescript": "^5.2.2", + "xo": "^0.56.0" }, "ava": { "serial": true + }, + "xo": { + "rules": { + "import/no-extraneous-dependencies": "off" + } } } diff --git a/test/test.js b/test/test.js index c0ffe69..6b2974a 100644 --- a/test/test.js +++ b/test/test.js @@ -1,22 +1,34 @@ /* eslint "ava/no-import-test-files": "off" */ +import {createRequire} from 'node:module'; +import process from 'node:process'; import test from 'ava'; import stripAnsi from 'strip-ansi'; import ansiEscapes from 'ansi-escapes'; import chalk from 'chalk'; -import defaultFixture from './fixtures/default.json'; -import noLineNumbers from './fixtures/no-line-numbers.json'; -import lineNumbers from './fixtures/line-numbers.json'; -import sortOrder from './fixtures/sort-by-severity-then-line-then-column.json'; -import messages from './fixtures/messages.json'; -import data from './fixtures/data.json'; -import eslintFormatterPretty from '..'; +import eslintFormatterPretty from '../index.js'; // eslint-disable-line import/order + +/// import defaultFixture from './fixtures/default.json'; +// import noLineNumbers from './fixtures/no-line-numbers.json'; +// import lineNumbers from './fixtures/line-numbers.json'; +// import sortOrder from './fixtures/sort-by-severity-then-line-then-column.json'; +// import messages from './fixtures/messages.json'; +// import data from './fixtures/data.json'; + +const require = createRequire(import.meta.url); + +const defaultFixture = require('./fixtures/default.json'); +const noLineNumbers = require('./fixtures/no-line-numbers.json'); +const lineNumbers = require('./fixtures/line-numbers.json'); +const sortOrder = require('./fixtures/sort-by-severity-then-line-then-column.json'); +const messages = require('./fixtures/messages.json'); +const data = require('./fixtures/data.json'); const fakeMessages = (desiredSeverity, desiredCount) => { const ofDesiredSeverity = messages.filter(({severity}) => severity === desiredSeverity); if (ofDesiredSeverity.length < desiredCount) { throw new Error( - `requested ${desiredCount} messages with severity ${desiredSeverity}. Only found ${desiredSeverity.length}.` + `requested ${desiredCount} messages with severity ${desiredSeverity}. Only found ${desiredSeverity.length}.`, ); } @@ -27,7 +39,10 @@ const fakeReport = (errorCount, warningCount) => ({ filePath: `${errorCount}-error.${warningCount}-warning.js`, errorCount, warningCount, - messages: fakeMessages(1, warningCount).concat(fakeMessages(2, errorCount)) + messages: [ + ...fakeMessages(1, warningCount), + ...fakeMessages(2, errorCount), + ], }); const enableHyperlinks = () => { @@ -93,10 +108,10 @@ test('sort by severity, then line number, then column number', t => { sanitized.indexOf('✖ 3:1'), sanitized.indexOf('✖ 30:1'), sanitized.indexOf('✖ 40:5'), - sanitized.indexOf('✖ 40:8') + sanitized.indexOf('✖ 40:8'), ]; console.log(output); - t.deepEqual(indexes, indexes.slice().sort((a, b) => a - b)); + t.deepEqual(indexes, [...indexes].sort((a, b) => a - b)); }); test('display warning total before error total', t => { @@ -105,10 +120,10 @@ test('display warning total before error total', t => { const sanitized = stripAnsi(output); const indexes = [ sanitized.indexOf('2 warnings'), - sanitized.indexOf('4 errors') + sanitized.indexOf('4 errors'), ]; console.log(output); - t.deepEqual(indexes, indexes.slice().sort((a, b) => a - b)); + t.deepEqual(indexes, [...indexes].sort((a, b) => a - b)); }); test('files will be sorted with least errors at the bottom, but zero errors at the top', t => { @@ -117,7 +132,7 @@ test('files will be sorted with least errors at the bottom, but zero errors at t fakeReport(1, 0), fakeReport(3, 0), fakeReport(0, 1), - fakeReport(2, 2) + fakeReport(2, 2), ]; const output = eslintFormatterPretty(reports); const sanitized = stripAnsi(output); @@ -125,11 +140,11 @@ test('files will be sorted with least errors at the bottom, but zero errors at t sanitized.indexOf('0-error.1-warning.js'), sanitized.indexOf('3-error.0-warning.js'), sanitized.indexOf('2-error.2-warning.js'), - sanitized.indexOf('1-error.0-warning.js') + sanitized.indexOf('1-error.0-warning.js'), ]; console.log(output); t.is(indexes.length, reports.length); - t.deepEqual(indexes, indexes.slice().sort((a, b) => a - b)); + t.deepEqual(indexes, [...indexes].sort((a, b) => a - b)); }); test('files with similar errorCounts will sort according to warningCounts', t => { @@ -142,7 +157,7 @@ test('files with similar errorCounts will sort according to warningCounts', t => fakeReport(0, 2), fakeReport(0, 3), fakeReport(2, 2), - fakeReport(2, 1) + fakeReport(2, 1), ]; const output = eslintFormatterPretty(reports); const sanitized = stripAnsi(output); @@ -154,11 +169,11 @@ test('files with similar errorCounts will sort according to warningCounts', t => sanitized.indexOf('2-error.1-warning.js'), sanitized.indexOf('1-error.2-warning.js'), sanitized.indexOf('1-error.1-warning.js'), - sanitized.indexOf('1-error.0-warning.js') + sanitized.indexOf('1-error.0-warning.js'), ]; console.log(output); t.is(indexes.length, reports.length); - t.deepEqual(indexes, indexes.slice().sort((a, b) => a - b)); + t.deepEqual(indexes, [...indexes].sort((a, b) => a - b)); }); test('use the `rulesMeta` property to get docs URL', t => {