@pezkuwi/dev 0.86.2 → 0.86.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.packageInfo = void 0;
4
- exports.packageInfo = { name: '@pezkuwi/dev', path: typeof __dirname === 'string' ? __dirname : 'auto', type: 'cjs', version: '0.86.2' };
4
+ exports.packageInfo = { name: '@pezkuwi/dev', path: typeof __dirname === 'string' ? __dirname : 'auto', type: 'cjs', version: '0.86.4' };
@@ -0,0 +1,78 @@
1
+ // Copyright 2017-2026 @pezkuwi/dev authors & contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ /** @type {Map<string, string>} */
5
+ const sources = new Map();
6
+
7
+ /**
8
+ * Widens the fix eslint core gives an unused disable directive.
9
+ *
10
+ * Core removes only the comment and puts a single space in its place
11
+ * (lib/linter/apply-disable-directives.js), so --fix turns
12
+ *
13
+ * const a = 1;
14
+ * // eslint-disable-next-line no-console
15
+ * const b = 2;
16
+ *
17
+ * into a whitespace-only line between the two statements, which the layout
18
+ * rules then leave behind as a blank line. Measured on the consumers after the
19
+ * eslint 10 move: 23 such lines in api alone, each removed by hand.
20
+ *
21
+ * Only a fix that removes a whole directive comment is changed: alone on its
22
+ * line, the line goes; after code, the space before it goes; before code, the
23
+ * space after it goes. Removing one rule name out of several is left as core
24
+ * makes it, as is every message with a rule id.
25
+ *
26
+ * @param {import('eslint').Linter.LintMessage} message
27
+ * @param {string} text the source the message was produced from
28
+ * @returns {import('eslint').Linter.LintMessage}
29
+ */
30
+ function widenDirectiveFix (message, text) {
31
+ if (message.ruleId !== null || message.fix?.text.trim() !== '') {
32
+ return message;
33
+ }
34
+
35
+ let [start, end] = message.fix.range;
36
+
37
+ if (!/^\/[/*]/.test(text.slice(start, end))) {
38
+ return message;
39
+ }
40
+
41
+ const lineStart = text.lastIndexOf('\n', start - 1) + 1;
42
+ const newline = text.indexOf('\n', end);
43
+ const lineEnd = newline === -1 ? text.length : newline;
44
+ const aloneBefore = text.slice(lineStart, start).trim() === '';
45
+ const aloneAfter = text.slice(end, lineEnd).trim() === '';
46
+
47
+ if (aloneBefore && aloneAfter) {
48
+ return { ...message, fix: { range: [lineStart, newline === -1 ? text.length : newline + 1], text: '' } };
49
+ } else if (aloneBefore) {
50
+ while (end < lineEnd && /[ \t]/.test(text[end] ?? '')) {
51
+ end++;
52
+ }
53
+ } else {
54
+ while (start > lineStart && /[ \t]/.test(text[start - 1] ?? '')) {
55
+ start--;
56
+ }
57
+ }
58
+
59
+ return { ...message, fix: { range: [start, end], text: '' } };
60
+ }
61
+
62
+ /** @type {import('eslint').Linter.Processor} */
63
+ export const directiveLines = {
64
+ meta: { name: '@pezkuwi/dev/directive-lines' },
65
+ postprocess (lists, filename) {
66
+ const text = sources.get(filename) ?? '';
67
+
68
+ sources.delete(filename);
69
+
70
+ return lists.flat().map((m) => widenDirectiveFix(m, text));
71
+ },
72
+ preprocess (text, filename) {
73
+ sources.set(filename, text);
74
+
75
+ return [text];
76
+ },
77
+ supportsAutofix: true
78
+ };
package/config/eslint.js CHANGED
@@ -22,8 +22,11 @@ import simpleImportSortPlugin from 'eslint-plugin-simple-import-sort';
22
22
  import sortDestructureKeysPlugin from 'eslint-plugin-sort-destructure-keys';
23
23
  import globals from 'globals';
24
24
 
25
+ import { directiveLines } from './eslint.directives.js';
25
26
  import { overrideAll, overrideJs, overrideJsx, overrideSpec } from './eslint.rules.js';
26
27
  import { standardRules } from './eslint.standard.js';
28
+ import { withoutContextualAssertions } from './eslint.typeAssertion.js';
29
+ import { withoutExportAllFix } from './eslint.typeExports.js';
27
30
 
28
31
  /**
29
32
  * eslint-plugin-header declares no options schema, and eslint 9+ rejects
@@ -93,7 +96,8 @@ export default [
93
96
  },
94
97
  plugins: {
95
98
  '@stylistic': stylisticPlugin,
96
- '@typescript-eslint': tsPlugin,
99
+ // see eslint.typeAssertion.js and eslint.typeExports.js
100
+ '@typescript-eslint': withoutExportAllFix(withoutContextualAssertions(tsPlugin)),
97
101
  // eslint-plugin-header reports at the first token after the leading
98
102
  // comments, so a generated file's /* eslint-disable */ covers it; the
99
103
  // maintained fork reports at line 1 instead. The compat layer supplies the
@@ -124,6 +128,8 @@ export default [
124
128
  },
125
129
  {
126
130
  files: extsToGlobs(EXT_ALL),
131
+ // see eslint.directives.js
132
+ processor: directiveLines,
127
133
  rules: {
128
134
  ...eslintJs.configs.recommended.rules,
129
135
  ...standardRules,
@@ -0,0 +1,65 @@
1
+ // Copyright 2017-2026 @pezkuwi/dev authors & contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ const RULE = 'no-unnecessary-type-assertion';
5
+ const ASSERTIONS = ['TSAsExpression', 'TSTypeAssertion'];
6
+
7
+ /**
8
+ * typescript-eslint 8 added a second check to no-unnecessary-type-assertion:
9
+ * an `x as T` is reported as unnecessary when the place it is passed to would
10
+ * accept `x` as it is ("the receiver accepts the original type"), and the
11
+ * autofix deletes the assertion. That is unsound when the assertion also
12
+ * drives inference around it: in
13
+ *
14
+ * const { validators } = found || { validators: {} as ValPoints };
15
+ *
16
+ * deleting `as ValPoints` leaves `validators` typed `{}`, and every use of it
17
+ * after that is an unresolved type. It also deletes assertions kept on
18
+ * purpose for another environment's typings (a Ledger transport under
19
+ * esm.sh). Measured across the consumers before this was added: 45 such
20
+ * reports, the first of them breaking the types of api-derive.
21
+ *
22
+ * This keeps the rule as it was before that check: an assertion to the type
23
+ * the expression already has is still reported and fixed, and so are non-null
24
+ * assertions the receiver makes unnecessary. Only the contextual report on
25
+ * `as` / `<T>` assertions is dropped.
26
+ *
27
+ * @template {object} P
28
+ * @param {P} plugin the @typescript-eslint plugin
29
+ * @returns {P}
30
+ */
31
+ export function withoutContextualAssertions (plugin) {
32
+ const rules = /** @type {Record<string, import('eslint').Rule.RuleModule>} */ (/** @type {{ rules: unknown }} */ (plugin).rules);
33
+ const rule = rules[RULE];
34
+
35
+ return /** @type {P} */ ({
36
+ ...plugin,
37
+ rules: {
38
+ ...rules,
39
+ [RULE]: {
40
+ ...rule,
41
+ /** @param {import('eslint').Rule.RuleContext} context */
42
+ create (context) {
43
+ const filtered = Object.create(context, {
44
+ report: {
45
+ value: (/** @type {import('eslint').Rule.ReportDescriptor} */ descriptor) => {
46
+ if (
47
+ 'messageId' in descriptor &&
48
+ descriptor.messageId === 'contextuallyUnnecessary' &&
49
+ 'node' in descriptor &&
50
+ ASSERTIONS.includes(descriptor.node.type)
51
+ ) {
52
+ return;
53
+ }
54
+
55
+ context.report(descriptor);
56
+ }
57
+ }
58
+ });
59
+
60
+ return rule.create(filtered);
61
+ }
62
+ }
63
+ }
64
+ });
65
+ }
@@ -0,0 +1,63 @@
1
+ // Copyright 2017-2026 @pezkuwi/dev authors & contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ const RULE = 'consistent-type-exports';
5
+
6
+ /**
7
+ * consistent-type-exports reports an `export * from './x'` whose module only
8
+ * exports types, and its autofix turns it into `export type * from './x'`.
9
+ * That changes what runs: `export *` loads the module, `export type *` is
10
+ * erased, so a module that only exports types but registers something when it
11
+ * loads (a Handlebars partial, an interface augmentation) silently stops being
12
+ * loaded. Measured on the consumers: the api typegen lost its `docs` partial
13
+ * this way and the build failed only at template time.
14
+ *
15
+ * Named re-exports do not have this problem: under verbatimModuleSyntax a
16
+ * type re-exported without `type` is already a compile error (TS1205).
17
+ *
18
+ * This keeps the report as it is and moves the `export *` fix into a
19
+ * suggestion, so --fix no longer applies it and an editor still offers it.
20
+ * Whoever accepts it decides whether the module needs a side-effect import.
21
+ *
22
+ * @template {object} P
23
+ * @param {P} plugin the @typescript-eslint plugin
24
+ * @returns {P}
25
+ */
26
+ export function withoutExportAllFix (plugin) {
27
+ const rules = /** @type {Record<string, import('eslint').Rule.RuleModule>} */ (/** @type {{ rules: unknown }} */ (plugin).rules);
28
+ const rule = rules[RULE];
29
+
30
+ return /** @type {P} */ ({
31
+ ...plugin,
32
+ rules: {
33
+ ...rules,
34
+ [RULE]: {
35
+ ...rule,
36
+ /** @param {import('eslint').Rule.RuleContext} context */
37
+ create (context) {
38
+ const filtered = Object.create(context, {
39
+ report: {
40
+ value: (/** @type {import('eslint').Rule.ReportDescriptor} */ descriptor) => {
41
+ if ('node' in descriptor && descriptor.node.type === 'ExportAllDeclaration' && descriptor.fix) {
42
+ const { fix, ...rest } = descriptor;
43
+
44
+ context.report({
45
+ ...rest,
46
+ suggest: [{ desc: 'Use `export type *` (the module is no longer loaded: add a side-effect import if it registers anything)', fix }]
47
+ });
48
+
49
+ return;
50
+ }
51
+
52
+ context.report(descriptor);
53
+ }
54
+ }
55
+ });
56
+
57
+ return rule.create(filtered);
58
+ },
59
+ meta: { ...rule.meta, hasSuggestions: true }
60
+ }
61
+ }
62
+ });
63
+ }
package/package.json CHANGED
@@ -15,7 +15,7 @@
15
15
  },
16
16
  "sideEffects": false,
17
17
  "type": "module",
18
- "version": "0.86.2",
18
+ "version": "0.86.4",
19
19
  "bin": {
20
20
  "pezkuwi-ci-ghact-build": "./scripts/pezkuwi-ci-ghact-build.mjs",
21
21
  "pezkuwi-ci-ghact-docs": "./scripts/pezkuwi-ci-ghact-docs.mjs",
@@ -63,8 +63,11 @@
63
63
  }
64
64
  },
65
65
  "./config/eslint": "./config/eslint.js",
66
+ "./config/eslint.directives": "./config/eslint.directives.js",
66
67
  "./config/eslint.rules": "./config/eslint.rules.js",
67
68
  "./config/eslint.standard": "./config/eslint.standard.js",
69
+ "./config/eslint.typeAssertion": "./config/eslint.typeAssertion.js",
70
+ "./config/eslint.typeExports": "./config/eslint.typeExports.js",
68
71
  "./config/prettier.cjs": "./config/prettier.cjs",
69
72
  "./config/prettier": "./config/prettier.cjs",
70
73
  "./config/rollup": "./config/rollup.js",
@@ -288,8 +291,8 @@
288
291
  "dependencies": {
289
292
  "@eslint/compat": "^2.1.1",
290
293
  "@eslint/js": "^10.0.1",
291
- "@pezkuwi/dev-test": "^0.86.2",
292
- "@pezkuwi/dev-ts": "^0.86.2",
294
+ "@pezkuwi/dev-test": "^0.86.4",
295
+ "@pezkuwi/dev-ts": "^0.86.4",
293
296
  "@rollup/plugin-alias": "^5.1.1",
294
297
  "@rollup/plugin-commonjs": "^29.0.3",
295
298
  "@rollup/plugin-dynamic-import-vars": "^2.1.5",
package/packageInfo.js CHANGED
@@ -1 +1 @@
1
- export const packageInfo = { name: '@pezkuwi/dev', path: (import.meta && import.meta.url) ? new URL(import.meta.url).pathname.substring(0, new URL(import.meta.url).pathname.lastIndexOf('/') + 1) : 'auto', type: 'esm', version: '0.86.2' };
1
+ export const packageInfo = { name: '@pezkuwi/dev', path: (import.meta && import.meta.url) ? new URL(import.meta.url).pathname.substring(0, new URL(import.meta.url).pathname.lastIndexOf('/') + 1) : 'auto', type: 'esm', version: '0.86.4' };
@@ -7,8 +7,6 @@ import yargs from 'yargs';
7
7
 
8
8
  import { __dirname, execPm, GITHUB_REPO, logBin } from './util.mjs';
9
9
 
10
- const TS_CONFIG_BUILD = true;
11
-
12
10
  logBin('pezkuwi-dev-run-lint');
13
11
 
14
12
  // Since yargs can also be a promise, we just relax the type here completely
@@ -36,5 +34,11 @@ if (!argv['skip-eslint']) {
36
34
  }
37
35
 
38
36
  if (!argv['skip-tsc']) {
39
- execPm(`pezkuwi-exec-tsc --noEmit --emitDeclarationOnly false --pretty${TS_CONFIG_BUILD ? ' --project tsconfig.build.json' : ''}`);
37
+ // The root tsconfig.build.json has `files: []` and only references the
38
+ // packages, so `tsc --project` on it checks no file at all. --build follows
39
+ // the references; it cannot be combined with --noEmit, so it writes the
40
+ // declarations the build would. Previous build output is removed first, since
41
+ // package configs without an `include` would otherwise pick it up as source.
42
+ execPm('pezkuwi-dev-clean-build');
43
+ execPm('pezkuwi-exec-tsc --build tsconfig.build.json --pretty');
40
44
  }