@zgeoff/oxlint-config 0.0.3 → 0.0.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.
package/README.md CHANGED
@@ -2,9 +2,10 @@
2
2
 
3
3
  Shareable [oxlint](https://oxc.rs/docs/guide/usage/linter.html) config: every category at `error`,
4
4
  restriction-rule cherry-picks, comment-style enforcement via `@stylistic`, and a bundled JS plugin
5
- (`zgeoff/*` rules) banning AST shapes no native oxlint rule can express — top-level arrows, nested
5
+ (`zgeoff/*` rules) banning shapes no native oxlint rule can express — top-level arrows, nested
6
6
  function declarations, bare named exports, destructured and inline-typed parameters, ternary and
7
- await arguments, and awaits hidden inside a control-flow condition or a `&&`/`||`/`??` chain.
7
+ await arguments, awaits hidden inside a control-flow condition or a `&&`/`||`/`??` chain, and
8
+ single-line `/** … */` blocks (auto-fixed to multi-line; inline `@type`/`@lends` casts exempt).
8
9
 
9
10
  ## Usage
10
11
 
package/oxlintrc.json CHANGED
@@ -25,6 +25,7 @@
25
25
  "zgeoff/no-await-args": "error",
26
26
  "zgeoff/no-await-in-condition": "error",
27
27
  "zgeoff/no-await-in-logical": "error",
28
+ "zgeoff/no-single-line-jsdoc": "error",
28
29
 
29
30
  // enforced comment style — line comments unless JSDoc or exclamation
30
31
  "@stylistic/multiline-comment-style": [
@@ -142,8 +143,9 @@
142
143
  {
143
144
  "files": ["**/*.js", "**/*.cjs", "**/*.mjs"],
144
145
  // untyped JS is `any` end to end, so the unsafe-* rules flag every
145
- // expression
146
+ // expression and strict-boolean-expressions every conditional
146
147
  "rules": {
148
+ "typescript/strict-boolean-expressions": "off",
147
149
  "typescript/no-unsafe-argument": "off",
148
150
  "typescript/no-unsafe-assignment": "off",
149
151
  "typescript/no-unsafe-call": "off",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zgeoff/oxlint-config",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Shareable oxlint config: strict categories, house-style rules, and a custom JS plugin",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -22,9 +22,16 @@
22
22
  "publishConfig": {
23
23
  "access": "public"
24
24
  },
25
+ "scripts": {
26
+ "typecheck": "tsc"
27
+ },
25
28
  "dependencies": {
26
29
  "@stylistic/eslint-plugin": "5.10.0"
27
30
  },
31
+ "devDependencies": {
32
+ "@types/bun": "1.3.14",
33
+ "typescript": "7.0.1-rc"
34
+ },
28
35
  "peerDependencies": {
29
36
  "oxlint": ">=1.71.0"
30
37
  }
package/plugin.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // Custom lint rules, loaded through oxlint's jsPlugins (ESLint v9 rule API).
2
- // Each rule bans an AST shape that no native oxlint rule can express, using
3
- // esquery selectors.
2
+ // Each rule bans a shape no native oxlint rule can express — esquery selectors
3
+ // for AST shapes, a source-comment scan where the target isn't
4
+ // esquery-selectable.
4
5
 
5
6
  function banSelectors(type, message, selectors) {
6
7
  return {
@@ -16,6 +17,82 @@ function banSelectors(type, message, selectors) {
16
17
  };
17
18
  }
18
19
 
20
+ // mirrors eslint-plugin-jsdoc's default singleLineTags: blocks that exist to
21
+ // cast inline (`/** @type {Foo} */ (bar)`) stay single-line by design
22
+ const inlineTagPattern = /^@(?:type|lends)\b/u;
23
+
24
+ function isSingleLineJSDoc(comment) {
25
+ return (
26
+ comment.type === 'Block' &&
27
+ comment.value.startsWith('*') &&
28
+ comment.loc.start.line === comment.loc.end.line
29
+ );
30
+ }
31
+
32
+ /**
33
+ * Builds the multi-line replacement for a single-line JSDoc comment, or null
34
+ * when the comment shares its line with code — expanding those in place would
35
+ * scramble the surrounding statement, so they are reported without a fix.
36
+ */
37
+ function planExpansion(text, comment) {
38
+ if (!isOwnLine(text, comment)) {
39
+ return null;
40
+ }
41
+
42
+ const lineStart = text.lastIndexOf('\n', comment.range[0] - 1) + 1;
43
+ const indent = text.slice(lineStart, comment.range[0]);
44
+ const body = comment.value.slice(1).trim();
45
+ const bodyLine = body === '' ? `${indent} *` : `${indent} * ${body}`;
46
+
47
+ return `/**\n${bodyLine}\n${indent} */`;
48
+ }
49
+
50
+ function isOwnLine(text, comment) {
51
+ const [start, end] = comment.range;
52
+ const lineStart = text.lastIndexOf('\n', start - 1) + 1;
53
+ const lineEnd = text.indexOf('\n', end);
54
+ const sliceEnd = lineEnd === -1 ? text.length : lineEnd;
55
+
56
+ return /^[ \t]*$/u.test(text.slice(lineStart, start)) && text.slice(end, sliceEnd).trim() === '';
57
+ }
58
+
59
+ const noSingleLineJSDoc = {
60
+ meta: {
61
+ type: 'layout',
62
+ fixable: 'whitespace',
63
+ messages: {
64
+ singleLine:
65
+ 'Write JSDoc blocks multi-line: `/**` alone, one `*`-prefixed line per point, `*/` alone.',
66
+ },
67
+ schema: [],
68
+ },
69
+ create(context) {
70
+ return {
71
+ Program() {
72
+ const offenders = context.sourceCode
73
+ .getAllComments()
74
+ .filter(
75
+ (comment) =>
76
+ isSingleLineJSDoc(comment) && !inlineTagPattern.test(comment.value.slice(1).trim()),
77
+ );
78
+
79
+ for (const comment of offenders) {
80
+ const replacement = planExpansion(context.sourceCode.text, comment);
81
+
82
+ context.report({
83
+ loc: comment.loc,
84
+ messageId: 'singleLine',
85
+ fix:
86
+ replacement === null
87
+ ? undefined
88
+ : (fixer) => fixer.replaceTextRange(comment.range, replacement),
89
+ });
90
+ }
91
+ },
92
+ };
93
+ },
94
+ };
95
+
19
96
  const plugin = {
20
97
  meta: { name: 'zgeoff' },
21
98
  rules: {
@@ -88,6 +165,7 @@ const plugin = {
88
165
  'Await into a named const instead of chaining it after a && / || / ?? operator.',
89
166
  ['LogicalExpression > AwaitExpression'],
90
167
  ),
168
+ 'no-single-line-jsdoc': noSingleLineJSDoc,
91
169
  },
92
170
  };
93
171