Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
2838607
change test-setup to ts
michaelfaith Jun 21, 2025
8582fab
add build config
michaelfaith Jun 21, 2025
046e58d
add types packages
michaelfaith Jun 21, 2025
d554cd3
rename files to ts
michaelfaith Jun 21, 2025
bc56d00
migrate fixer-return
michaelfaith Jun 21, 2025
7e346f5
migrate consistent-output
michaelfaith Jun 21, 2025
98cf8a3
migrate meta-property-ordering
michaelfaith Jun 22, 2025
ce6e93b
migrate no-deprecated-context-methods
michaelfaith Jun 22, 2025
baedd5b
migrate no-deprecated-report-api
michaelfaith Jun 22, 2025
5cee9cd
migrate no-identical-tests
michaelfaith Jun 22, 2025
cb1d41d
migrate no-meta-replaced-by
michaelfaith Jun 22, 2025
562fa66
migrate no-meta-schema-default
michaelfaith Jun 22, 2025
9c5e68e
migrate no-missing-message-ids
michaelfaith Jun 23, 2025
3694a1e
migrate no-missing-placeholders
michaelfaith Jun 24, 2025
02314b4
migrate no-only-tests
michaelfaith Jun 24, 2025
d86155c
migrate no-property-in-node
michaelfaith Jun 24, 2025
326cfce
migrate no-unused-message-ids
michaelfaith Jun 30, 2025
dbac215
migrate no-unused-placeholders
michaelfaith Jul 1, 2025
912ab48
migrate no-useless-token-range
michaelfaith Jul 1, 2025
16430e0
migrate prefer-message-ids
michaelfaith Jul 4, 2025
c3b8257
migrate prefer-object-rule
michaelfaith Jul 4, 2025
6080c2a
migrate prefer-output-null
michaelfaith Jul 4, 2025
dbab18b
migrate prefer-placeholders
michaelfaith Jul 6, 2025
4a187f5
migrate prefer-replace-text
michaelfaith Jul 7, 2025
10b1135
migrate report-message-format
michaelfaith Jul 9, 2025
80b563c
migrate require-meta-default-options
michaelfaith Jul 11, 2025
900ae8c
migrate require-meta-docs-description
michaelfaith Jul 11, 2025
0eb77cc
migrate prefer-replace-text
michaelfaith Jul 11, 2025
11fdcbf
migrate require-meta-docs-recommended
michaelfaith Jul 12, 2025
1ce2806
migrate require-meta-docs-url
michaelfaith Jul 14, 2025
ab52425
migrate require-meta-fixable
michaelfaith Jul 14, 2025
b797187
migrate require-meta-has-suggestions
michaelfaith Jul 15, 2025
44c1bb4
migrate require-meta-schema-description
michaelfaith Jul 15, 2025
7404dec
migrate require-meta-schema
michaelfaith Jul 15, 2025
db48780
migrate require-meta-type
michaelfaith Jul 15, 2025
6f0cd12
migrate test-case-property-ordering
michaelfaith Jul 15, 2025
cf361fa
migrate test-case-shorthand-strings
michaelfaith Jul 16, 2025
833e1d1
migrate plugin (index)
michaelfaith Jul 16, 2025
0fe29c2
migrate indext.ts test
michaelfaith Jul 17, 2025
b1ea5c4
git mv all rule tests
michaelfaith Jul 17, 2025
02e1d60
fix type issues in no-meta-replaced-by test
michaelfaith Jul 17, 2025
6077cd4
fix type issues with no-missing-placeholders tests
michaelfaith Jul 18, 2025
c460aff
fix type issues with no-unused-placeholders tests
michaelfaith Jul 18, 2025
aa681ad
fix type issues with no-useless-token-range tests
michaelfaith Jul 18, 2025
be5b2bd
fix type issues with report-message-format tests
michaelfaith Jul 18, 2025
96987fa
remove invalid case from valid array
michaelfaith Jul 18, 2025
6035735
fix type issues with test-case-shorthand-strings
michaelfaith Jul 18, 2025
9e11e48
fix type issues in rule-setup tests
michaelfaith Jul 19, 2025
1c64770
Add explicit extensions to imports without them
michaelfaith Jul 19, 2025
30a79ee
fix type issues in utils tests
michaelfaith Jul 20, 2025
2b1d318
fix merge issue
michaelfaith Jul 23, 2025
ba1d108
adjust import order
michaelfaith Jul 23, 2025
93dd0ac
fix plugin type
michaelfaith Jul 23, 2025
ac937db
fix utils tests
michaelfaith Jul 23, 2025
e97458e
switch to tsup for build
michaelfaith Jul 23, 2025
cb5eda9
Change import of `package.json` to require, for backwards compatibility
michaelfaith Jul 26, 2025
ff74f46
fix utils tests
michaelfaith Jul 26, 2025
4bfc4b0
update rule-setup tests
michaelfaith Jul 27, 2025
4da3c01
add build to publish workflow
michaelfaith Jul 27, 2025
9a6060c
add slashes to .gitignore
michaelfaith Jul 28, 2025
0259d01
remove jsdoc type annotation from `fixer-return`
michaelfaith Jul 28, 2025
2559024
remove unnecessary param from `no-indentical-tests`
michaelfaith Jul 28, 2025
a3cf33a
add early return in `no-missing-placeholders`
michaelfaith Jul 28, 2025
0052ccf
removed assert in `no-only-tests`
michaelfaith Jul 28, 2025
ead0948
remove empty param annotation from `no-property-in-node`
michaelfaith Jul 28, 2025
d91a0af
remove casting from no-unused-message-ids
michaelfaith Jul 30, 2025
1ffacf7
add back valid test case to require-meta-type
michaelfaith Jul 30, 2025
ff19420
add explanatory comment to estree.d.ts
michaelfaith Jul 30, 2025
64429ad
remove type annotation from comment in test-case-shorthand-string
michaelfaith Jul 30, 2025
3768cfa
removed casting from no-meta-schema-default
michaelfaith Jul 30, 2025
2f80268
remove casts from no-useless-token-range
michaelfaith Jul 31, 2025
1abe593
remove cast from report-message-format
michaelfaith Jul 31, 2025
22cf264
remove cast from require-meta-default-options
michaelfaith Aug 1, 2025
88bc975
remove cast from require-meta-docs-url
michaelfaith Aug 1, 2025
9f0af6f
remove casts from require-meta-fixables
michaelfaith Aug 1, 2025
fb85ff6
remove cast from require-meta-type
michaelfaith Aug 1, 2025
1f22290
Adjust PartialRuleInfo types
michaelfaith Aug 1, 2025
6890444
addressed feedback in utils
michaelfaith Aug 2, 2025
ce4f96f
ci: add typecheck step to CI workflow
michaelfaith Aug 3, 2025
11fe179
removed unneeded ts-expect-error from eslint.config.ts
michaelfaith Aug 3, 2025
f287566
Address feedback in utils
michaelfaith Aug 3, 2025
9eeef96
remove non-null assertion from require-meta-docs-recommended
michaelfaith Aug 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,13 @@ jobs:
node-version: 'lts/*'
- run: npm install
- run: npm run test:remote

typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 'lts/*'
- run: npm install
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically, I believe we should be using npm ci in these workflows. Fine to consider it as a follow-up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I just matched the other steps. Could be a follow-up that changes them all to ci?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking a bit more on this later. In order to use npm ci, it'll require than just updating the calls in the workflow. The repo would need to start maintaining a lock file. It currently has lock files explicitly disabled, and npm ci requires that. I personally don't think having a lock file is a bad thing. I was kind of surprised when I first realized there wasn't one. But it seemed to be a conscious choice someone made at some point.

The other consideration is that using npm ci in the builds will result in longer build times, if that's important to you.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh good catch. I now remember that's why we don't use npm ci. Let's avoid adding it at this time. For us, it's fine to be more aggressive with dependency updates and omit a lockfile.

- run: npm run typecheck
1 change: 1 addition & 0 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ jobs:
if: ${{ steps.release.outputs.release_created }}
- run: |
npm install --force
npm run build
npm publish --provenance
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Expand Down
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
.idea
coverage
.idea/
coverage/
.vscode
node_modules/
npm-debug.log
yarn.lock
.eslintcache
dist/

# eslint-remote-tester
eslint-remote-tester-results
3 changes: 1 addition & 2 deletions eslint.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { FlatCompat } from '@eslint/eslintrc';
import { defineConfig } from 'eslint/config';
import markdown from 'eslint-plugin-markdown';
import pluginN from 'eslint-plugin-n';
// @ts-expect-error - eslint-plugin is not typed yet
import eslintPlugin from './lib/index.js';

const dirname = path.dirname(fileURLToPath(import.meta.url));
Expand All @@ -17,7 +16,7 @@ const compat = new FlatCompat({
export default defineConfig([
// Global ignores
{
ignores: ['node_modules', 'coverage'],
ignores: ['node_modules', 'coverage', 'dist'],
},
// Global settings
{
Expand Down
89 changes: 56 additions & 33 deletions lib/index.js → lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@
* @fileoverview An ESLint plugin for linting ESLint plugins
* @author Teddy Katz
*/
import { createRequire } from 'node:module';

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------
import type { ESLint, Linter, Rule } from 'eslint';

import packageMetadata from '../package.json' with { type: 'json' };
import consistentOutput from './rules/consistent-output.js';
import fixerReturn from './rules/fixer-return.js';
import metaPropertyOrdering from './rules/meta-property-ordering.js';
Expand Down Expand Up @@ -41,20 +39,56 @@ import requireMetaType from './rules/require-meta-type.js';
import testCasePropertyOrdering from './rules/test-case-property-ordering.js';
import testCaseShorthandStrings from './rules/test-case-shorthand-strings.js';

const require = createRequire(import.meta.url);

const packageMetadata = require("../package.json") as {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had issues with this which I fixed in qunitjs/eslint-plugin-qunit#584

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That doesn't really apply in this case, since this file's relative position to the package.json is the same in the dist folder as it is in the lib folder.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Looks like I should be able to move index.js to fix that in my project:

name: string;
version: string;
};

const PLUGIN_NAME = packageMetadata.name.replace(/^eslint-plugin-/, '');
const CONFIG_NAMES = [
'all',
'all-type-checked',
'recommended',
'rules',
'tests',
'rules-recommended',
'tests-recommended',
] as const;
type ConfigName = (typeof CONFIG_NAMES)[number];

const configFilters = {
all: (rule) => !rule.meta.docs.requiresTypeChecking,
const configFilters: Record<ConfigName, (rule: Rule.RuleModule) => boolean> = {
all: (rule: Rule.RuleModule) =>
!(
rule.meta?.docs &&
'requiresTypeChecking' in rule.meta.docs &&
rule.meta.docs.requiresTypeChecking
),
'all-type-checked': () => true,
recommended: (rule) => rule.meta.docs.recommended,
rules: (rule) => rule.meta.docs.category === 'Rules',
tests: (rule) => rule.meta.docs.category === 'Tests',
'rules-recommended': (rule) =>
recommended: (rule: Rule.RuleModule) => !!rule.meta?.docs?.recommended,
rules: (rule: Rule.RuleModule) => rule.meta?.docs?.category === 'Rules',
tests: (rule: Rule.RuleModule) => rule.meta?.docs?.category === 'Tests',
'rules-recommended': (rule: Rule.RuleModule) =>
configFilters.recommended(rule) && configFilters.rules(rule),
'tests-recommended': (rule) =>
'tests-recommended': (rule: Rule.RuleModule) =>
configFilters.recommended(rule) && configFilters.tests(rule),
};

const createConfig = (configName: ConfigName): Linter.Config => ({
name: `${PLUGIN_NAME}/${configName}`,
plugins: {
get [PLUGIN_NAME](): ESLint.Plugin {
return plugin;
},
},
rules: Object.fromEntries(
(Object.keys(allRules) as (keyof typeof allRules)[])
.filter((ruleName) => configFilters[configName](allRules[ruleName]))
.map((ruleName) => [`${PLUGIN_NAME}/${ruleName}`, 'error']),
),
});

// ------------------------------------------------------------------------------
// Plugin Definition
// ------------------------------------------------------------------------------
Expand Down Expand Up @@ -93,34 +127,23 @@ const allRules = {
'require-meta-type': requireMetaType,
'test-case-property-ordering': testCasePropertyOrdering,
'test-case-shorthand-strings': testCaseShorthandStrings,
};
} satisfies Record<string, Rule.RuleModule>;

/** @type {import("eslint").ESLint.Plugin} */
const plugin = {
meta: {
name: packageMetadata.name,
version: packageMetadata.version,
},
rules: allRules,
configs: {}, // assigned later
};

// configs
Object.assign(
plugin.configs,
Object.keys(configFilters).reduce((configs, configName) => {
return Object.assign(configs, {
[configName]: {
name: `${PLUGIN_NAME}/${configName}`,
plugins: { [PLUGIN_NAME]: plugin },
rules: Object.fromEntries(
Object.keys(allRules)
.filter((ruleName) => configFilters[configName](allRules[ruleName]))
.map((ruleName) => [`${PLUGIN_NAME}/${ruleName}`, 'error']),
),
},
});
}, {}),
);
configs: {
all: createConfig('all'),
'all-type-checked': createConfig('all-type-checked'),
recommended: createConfig('recommended'),
rules: createConfig('rules'),
tests: createConfig('tests'),
'rules-recommended': createConfig('rules-recommended'),
'tests-recommended': createConfig('tests-recommended'),
},
} satisfies ESLint.Plugin;

export default plugin;
20 changes: 10 additions & 10 deletions lib/rules/consistent-output.js → lib/rules/consistent-output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@
* @fileoverview Enforce consistent use of `output` assertions in rule tests
* @author Teddy Katz
*/
import type { Rule } from 'eslint';

import { getKeyName, getTestInfo } from '../utils.js';

const keyNameMapper = (property: Parameters<typeof getKeyName>[0]) =>
getKeyName(property);

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

/** @type {import('eslint').Rule.RuleModule} */
const rule = {
const rule: Rule.RuleModule = {
meta: {
type: 'suggestion',
docs: {
Expand All @@ -20,7 +22,7 @@ const rule = {
recommended: false,
url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/consistent-output.md',
},
fixable: null, // or "code" or "whitespace"
fixable: undefined, // or "code" or "whitespace"
schema: [
{
type: 'string',
Expand All @@ -37,20 +39,18 @@ const rule = {
},

create(context) {
// ----------------------------------------------------------------------
// Public
// ----------------------------------------------------------------------
const always = context.options[0] && context.options[0] === 'always';
const always: boolean =
context.options[0] && context.options[0] === 'always';

return {
Program(ast) {
getTestInfo(context, ast).forEach((testRun) => {
const readableCases = testRun.invalid.filter(
(testCase) => testCase.type === 'ObjectExpression',
(testCase) => testCase?.type === 'ObjectExpression',
);
const casesWithoutOutput = readableCases.filter(
(testCase) =>
!testCase.properties.map(getKeyName).includes('output'),
!testCase.properties.map(keyNameMapper).includes('output'),
);

if (
Expand Down
67 changes: 39 additions & 28 deletions lib/rules/fixer-return.js → lib/rules/fixer-return.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,37 @@
* @fileoverview require fixer functions to return a fix
* @author 薛定谔的猫<[email protected]>
*/

import { getStaticValue } from '@eslint-community/eslint-utils';
import type { Rule } from 'eslint';
import type {
ArrowFunctionExpression,
FunctionExpression,
Identifier,
Node,
Position,
SourceLocation,
} from 'estree';

import {
getContextIdentifiers,
isAutoFixerFunction,
isSuggestionFixerFunction,
} from '../utils.js';
import type { FunctionInfo } from '../types.js';

const DEFAULT_FUNC_INFO: FunctionInfo = {
upper: null,
codePath: null,
hasReturnWithFixer: false,
hasYieldWithFixer: false,
shouldCheck: false,
node: null,
};

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

/** @type {import('eslint').Rule.RuleModule} */
const rule = {
const rule: Rule.RuleModule = {
meta: {
type: 'problem',
docs: {
Expand All @@ -25,36 +41,32 @@ const rule = {
recommended: true,
url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/fixer-return.md',
},
fixable: null,
fixable: undefined,
schema: [],
messages: {
missingFix: 'Fixer function never returned a fix.',
},
},

create(context) {
let funcInfo = {
upper: null,
codePath: null,
hasReturnWithFixer: false,
hasYieldWithFixer: false,
shouldCheck: false,
node: null,
};
let contextIdentifiers;
let funcInfo: FunctionInfo = DEFAULT_FUNC_INFO;
let contextIdentifiers = new Set<Identifier>();

/**
* As we exit the fix() function, ensure we have returned or yielded a real fix by this point.
* If not, report the function as a violation.
*
* @param {ASTNode} node - A node to check.
* @param {Location} loc - Optional location to report violation on.
* @returns {void}
* @param node - A node to check.
* @param loc - Optional location to report violation on.
*/
function ensureFunctionReturnedFix(
node,
loc = (node.id || node).loc.start,
) {
node: ArrowFunctionExpression | FunctionExpression,
loc: Position | SourceLocation | undefined = (node.type ===
'FunctionExpression' && node.id
? node.id
: node
).loc?.start,
): void {
if (
(node.generator && !funcInfo.hasYieldWithFixer) || // Generator function never yielded a fix
(!node.generator && !funcInfo.hasReturnWithFixer) // Non-generator function never returned a fix
Expand All @@ -70,10 +82,9 @@ const rule = {
/**
* Check if a returned/yielded node is likely to be a fix or not.
* A fix is an object created by fixer.replaceText() for example and returned by the fix function.
* @param {ASTNode} node - node to check
* @returns {boolean}
* @param node - node to check
*/
function isFix(node) {
function isFix(node: Node): boolean {
if (node.type === 'ArrayExpression' && node.elements.length === 0) {
// An empty array is not a fix.
return false;
Expand Down Expand Up @@ -104,22 +115,22 @@ const rule = {
},

// Stacks this function's information.
onCodePathStart(codePath, node) {
onCodePathStart(codePath: Rule.CodePath, node: Node) {
funcInfo = {
upper: funcInfo,
codePath,
hasYieldWithFixer: false,
hasReturnWithFixer: false,
shouldCheck:
isAutoFixerFunction(node, contextIdentifiers) ||
isSuggestionFixerFunction(node, contextIdentifiers),
isAutoFixerFunction(node, contextIdentifiers, context) ||
isSuggestionFixerFunction(node, contextIdentifiers, context),
node,
};
},

// Pops this function's information.
onCodePathEnd() {
funcInfo = funcInfo.upper;
funcInfo = funcInfo.upper ?? DEFAULT_FUNC_INFO;
},

// Yield in generators
Expand Down Expand Up @@ -147,7 +158,7 @@ const rule = {
'ArrowFunctionExpression:exit'(node) {
if (funcInfo.shouldCheck) {
const sourceCode = context.sourceCode;
const loc = sourceCode.getTokenBefore(node.body).loc; // Show violation on arrow (=>).
const loc = sourceCode.getTokenBefore(node.body)?.loc; // Show violation on arrow (=>).
if (node.expression) {
// When the return is implied (no curly braces around the body), we have to check the single body node directly.
if (!isFix(node.body)) {
Expand Down
Loading