Skip to content

Commit d543a4b

Browse files
authored
feat: add type-checking with jsdoc TypeScript types and publish types (#571)
* feat: add jsdoc typescript types * fix * fix * fixes * fix eslint-remote-tester types * fix * fix * export types * fix exported types * add sample config * tweak * fix exported type * fix
1 parent 572305f commit d543a4b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+1523
-216
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@ npm-debug.log
66

77
# eslint-remote-tester
88
/eslint-remote-tester-results/
9+
10+
# TypeScript output
11+
dist/

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,16 @@ For more details on how to extend your configuration from a plugin configuration
1515

1616
| | Name | Description |
1717
| :--- | :--- | :--- |
18-
|| `recommended` | This configuration includes rules which I recommend to avoid QUnit runtime errors or incorrect behavior, some of which can be difficult to debug. Some of these rules also encourage best practices that help QUnit work better for you. For ESLint `.eslintrc.js` legacy config, extend from `"plugin:qunit/recommended"`. For ESLint `eslint.config.js` flat config, load from `require('eslint-plugin-qunit/configs/recommended')`. |
18+
|| `recommended` | This configuration includes rules which I recommend to avoid QUnit runtime errors or incorrect behavior, some of which can be difficult to debug. Some of these rules also encourage best practices that help QUnit work better for you. For ESLint `.eslintrc.js` legacy config, extend from `"plugin:qunit/recommended"`. For ESLint `eslint.config.js` or `eslint.config.ts` flat config, load from `require('eslint-plugin-qunit/configs/recommended')`. |
19+
20+
```ts
21+
// eslint.config.ts
22+
import eslintPluginQunitRecommended from 'eslint-plugin-qunit/configs/recommended';
23+
24+
export default [
25+
eslintPluginQunitRecommended,
26+
];
27+
```
1928

2029
## Rules
2130

eslint-remote-tester.config.js renamed to eslint-remote-tester.config.mjs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
"use strict";
2-
3-
const eslintPluginQunitRecommended = require("./lib/configs/recommended");
1+
import eslintPluginQunitRecommended from "./lib/configs/recommended.js";
42

53
/** @type {import('eslint-remote-tester').Config} */
6-
module.exports = {
4+
const config = {
75
/** Repositories to scan */
86
repositories: [
97
// A few dozen top repositories using QUnit or this plugin.
@@ -36,3 +34,5 @@ module.exports = {
3634
...eslintPluginQunitRecommended,
3735
},
3836
};
37+
38+
export default config;

eslint.config.js

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
"use strict";
22

33
const js = require("@eslint/js");
4+
5+
// @ts-expect-error -- TODO: no types yet
46
const eslintPluginEslintComments = require("@eslint-community/eslint-plugin-eslint-comments/configs");
7+
8+
// @ts-expect-error -- TODO: no types yet -- https://github.com/eslint-community/eslint-plugin-eslint-plugin/issues/310
59
const eslintPluginEslintPluginAll = require("eslint-plugin-eslint-plugin/configs/all");
610
const eslintPluginMarkdown = require("eslint-plugin-markdown");
711
const eslintPluginMocha = require("eslint-plugin-mocha");
@@ -41,21 +45,6 @@ module.exports = [
4145
eqeqeq: "error",
4246
"func-style": ["error", "declaration"],
4347
"guard-for-in": "error",
44-
"lines-around-comment": [
45-
"error",
46-
{
47-
beforeBlockComment: false,
48-
afterBlockComment: false,
49-
beforeLineComment: true,
50-
afterLineComment: false,
51-
allowBlockStart: true,
52-
allowBlockEnd: true,
53-
allowObjectStart: true,
54-
allowObjectEnd: true,
55-
allowArrayStart: true,
56-
allowArrayEnd: true,
57-
},
58-
],
5948
"max-depth": ["error", 5],
6049
"new-cap": ["error", { newIsCap: true, capIsNew: true }],
6150
"no-array-constructor": "error",
@@ -111,7 +100,6 @@ module.exports = [
111100
"no-throw-literal": "error",
112101
"no-trailing-spaces": "error",
113102
"no-undef": "error",
114-
"no-undefined": "error",
115103
"no-underscore-dangle": "error",
116104
"no-unexpected-multiline": "error",
117105
"no-unmodified-loop-condition": "error",
@@ -209,4 +197,10 @@ module.exports = [
209197
strict: "off",
210198
},
211199
},
200+
{
201+
files: ["**/*.mjs"],
202+
languageOptions: {
203+
sourceType: "module",
204+
},
205+
},
212206
];

index.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
"use strict";
99

1010
const requireIndex = require("requireindex");
11+
12+
// @ts-expect-error -- ESM/TypeScript conversion should fix this.
1113
const pkg = require("./package.json");
1214

1315
module.exports = {
@@ -22,7 +24,7 @@ module.exports = {
2224
configs: {
2325
recommended: {
2426
plugins: ["qunit"],
25-
rules: {
27+
rules: /** @type {import('eslint').Linter.RulesRecord} */ ({
2628
"qunit/assert-args": "error",
2729
"qunit/literal-compare-order": "error",
2830
"qunit/no-assert-equal": "error",
@@ -58,7 +60,7 @@ module.exports = {
5860
"qunit/require-expect": "error",
5961
"qunit/require-object-in-propequal": "error",
6062
"qunit/resolve-async": "error",
61-
},
63+
}),
6264
},
6365
},
6466
};

lib/rules/assert-args.js

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,14 @@ module.exports = {
3535
},
3636

3737
create: function (context) {
38+
/** @type {Array<{assertContextVar: string | null}>} */
3839
const testStack = [],
3940
sourceCode = context.getSourceCode();
4041

42+
/**
43+
* @param {import('estree').Node} argNode
44+
* @returns {import('estree').Node}
45+
*/
4146
function isPossibleMessage(argNode) {
4247
// For now, we will allow all nodes. Hoping to allow user-driven
4348
// configuration later.
@@ -48,16 +53,31 @@ module.exports = {
4853
return argNode;
4954
}
5055

56+
/**
57+
* @returns {string | null}
58+
*/
5159
function getAssertContext() {
5260
assert.ok(testStack.length);
5361

5462
return testStack[testStack.length - 1].assertContextVar;
5563
}
5664

65+
/**
66+
* @param {import('estree').Node} callExpressionNode
67+
*/
5768
function checkAssertArity(callExpressionNode) {
69+
if (callExpressionNode.type !== "CallExpression") {
70+
return;
71+
}
72+
73+
const assertContextVar = getAssertContext();
74+
if (!assertContextVar) {
75+
return;
76+
}
77+
5878
const allowedArities = utils.getAllowedArities(
5979
callExpressionNode.callee,
60-
getAssertContext(),
80+
assertContextVar,
6181
),
6282
assertArgs = callExpressionNode.arguments,
6383
lastArg = assertArgs[assertArgs.length - 1],
@@ -84,7 +104,7 @@ module.exports = {
84104
: "unexpectedArgCountNoMessage",
85105
data: {
86106
callee: sourceCode.getText(callExpressionNode.callee),
87-
argCount: assertArgs.length,
107+
argCount: assertArgs.length.toString(),
88108
},
89109
});
90110
}
@@ -97,11 +117,14 @@ module.exports = {
97117
node.arguments,
98118
),
99119
});
100-
} else if (
101-
testStack.length > 0 &&
102-
utils.isAssertion(node.callee, getAssertContext())
103-
) {
104-
checkAssertArity(node);
120+
} else if (testStack.length > 0) {
121+
const assertContext = getAssertContext();
122+
if (
123+
assertContext &&
124+
utils.isAssertion(node.callee, assertContext)
125+
) {
126+
checkAssertArity(node);
127+
}
105128
}
106129
},
107130

lib/rules/literal-compare-order.js

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,6 @@ const assert = require("node:assert"),
1515
// Rule Definition
1616
//------------------------------------------------------------------------------
1717

18-
function swapFirstTwoNodesInList(sourceCode, fixer, list) {
19-
const node0Text = sourceCode.getText(list[0]);
20-
const node1Text = sourceCode.getText(list[1]);
21-
return [
22-
fixer.replaceText(list[0], node1Text),
23-
fixer.replaceText(list[1], node0Text),
24-
];
25-
}
26-
2718
/** @type {import('eslint').Rule.RuleModule} */
2819
module.exports = {
2920
meta: {
@@ -45,6 +36,7 @@ module.exports = {
4536
},
4637

4738
create: function (context) {
39+
/** @type {Array<{assertContextVar: string | null}>} */
4840
const testStack = [],
4941
sourceCode = context.getSourceCode();
5042

@@ -54,6 +46,24 @@ module.exports = {
5446
return testStack[testStack.length - 1].assertContextVar;
5547
}
5648

49+
/**
50+
* @param {any} fixer
51+
* @param {import('estree').Node[]} list
52+
* @returns {import('eslint').Rule.Fix[]}
53+
*/
54+
function swapFirstTwoNodesInList(fixer, list) {
55+
const node0Text = sourceCode.getText(list[0]);
56+
const node1Text = sourceCode.getText(list[1]);
57+
return [
58+
fixer.replaceText(list[0], node1Text),
59+
fixer.replaceText(list[1], node0Text),
60+
];
61+
}
62+
63+
/**
64+
* @param {import('estree').Node[]} args
65+
* @param {boolean} compareActualFirst
66+
*/
5767
function checkLiteralCompareOrder(args, compareActualFirst) {
5868
if (args.length < 2) {
5969
return;
@@ -73,7 +83,7 @@ module.exports = {
7383
actual: sourceCode.getText(args[1]),
7484
},
7585
fix(fixer) {
76-
return swapFirstTwoNodesInList(sourceCode, fixer, args);
86+
return swapFirstTwoNodesInList(fixer, args);
7787
},
7888
});
7989
} else if (
@@ -89,19 +99,29 @@ module.exports = {
8999
actual: sourceCode.getText(args[1]),
90100
},
91101
fix(fixer) {
92-
return swapFirstTwoNodesInList(sourceCode, fixer, args);
102+
return swapFirstTwoNodesInList(fixer, args);
93103
},
94104
});
95105
}
96106
}
97107

108+
/**
109+
* @param {import('eslint').Rule.Node} node
110+
* @param {string | null} assertVar
111+
*/
98112
function processAssertion(node, assertVar) {
113+
if (node.type !== "CallExpression") {
114+
return;
115+
}
116+
99117
/* istanbul ignore else: correctly does nothing */
100-
if (utils.isComparativeAssertion(node.callee, assertVar)) {
101-
const compareActualFirst = utils.shouldCompareActualFirst(
102-
node.callee,
103-
assertVar,
104-
);
118+
if (
119+
!assertVar ||
120+
utils.isComparativeAssertion(node.callee, assertVar)
121+
) {
122+
const compareActualFirst =
123+
!assertVar ||
124+
utils.shouldCompareActualFirst(node.callee, assertVar);
105125
checkLiteralCompareOrder(node.arguments, compareActualFirst);
106126
}
107127
}
@@ -115,11 +135,14 @@ module.exports = {
115135
node.arguments,
116136
),
117137
});
118-
} else if (
119-
testStack.length > 0 &&
120-
utils.isAssertion(node.callee, getAssertContext())
121-
) {
122-
processAssertion(node, getAssertContext());
138+
} else if (testStack.length > 0) {
139+
const assertVar = getAssertContext();
140+
if (
141+
!assertVar ||
142+
utils.isAssertion(node.callee, assertVar)
143+
) {
144+
processAssertion(node, assertVar);
145+
}
123146
}
124147
},
125148

0 commit comments

Comments
 (0)