homebridge-plugin-utils 1.35.0 → 2.0.0

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.
Files changed (198) hide show
  1. package/README.md +130 -2
  2. package/build/eslint-plugin/README.md +164 -0
  3. package/build/eslint-plugin/config.mjs +308 -0
  4. package/build/eslint-plugin/index.mjs +7 -0
  5. package/build/eslint-plugin/plugin.mjs +46 -0
  6. package/build/eslint-plugin/rules/blank-line-after-open-brace.mjs +137 -0
  7. package/build/eslint-plugin/rules/blank-line-after-open-brace.test.mjs +112 -0
  8. package/build/eslint-plugin/rules/comment-style.mjs +190 -0
  9. package/build/eslint-plugin/rules/comment-style.test.mjs +190 -0
  10. package/build/eslint-plugin/rules/enforce-node-protocol.mjs +114 -0
  11. package/build/eslint-plugin/rules/enforce-node-protocol.test.mjs +116 -0
  12. package/build/eslint-plugin/rules/paren-comparisons-in-logical.mjs +94 -0
  13. package/build/eslint-plugin/rules/paren-comparisons-in-logical.test.mjs +107 -0
  14. package/build/eslint-plugin/rules/split-type-imports.mjs +354 -0
  15. package/build/eslint-plugin/rules/split-type-imports.test.mjs +268 -0
  16. package/build/eslint-plugin/test-setup.mjs +21 -0
  17. package/build/fs-ops.mjs +184 -0
  18. package/build/tsconfig.json +18 -3
  19. package/dist/backpressure.d.ts +94 -45
  20. package/dist/backpressure.js +229 -93
  21. package/dist/backpressure.js.map +1 -1
  22. package/dist/cli/index.d.ts +95 -0
  23. package/dist/cli/index.js +355 -0
  24. package/dist/cli/index.js.map +1 -0
  25. package/dist/clock-double.d.ts +93 -0
  26. package/dist/clock-double.js +141 -0
  27. package/dist/clock-double.js.map +1 -0
  28. package/dist/clock.d.ts +39 -0
  29. package/dist/clock.js +34 -0
  30. package/dist/clock.js.map +1 -0
  31. package/dist/eslint-plugin/config.d.mts +193 -0
  32. package/dist/eslint-plugin/index.d.mts +2 -0
  33. package/dist/eslint-plugin/plugin.d.mts +87 -0
  34. package/dist/eslint-plugin/rules/blank-line-after-open-brace.d.mts +19 -0
  35. package/dist/eslint-plugin/rules/comment-style.d.mts +15 -0
  36. package/dist/eslint-plugin/rules/enforce-node-protocol.d.mts +18 -0
  37. package/dist/eslint-plugin/rules/paren-comparisons-in-logical.d.mts +15 -0
  38. package/dist/eslint-plugin/rules/split-type-imports.d.mts +16 -0
  39. package/dist/featureOptions-docs.d.ts +96 -0
  40. package/dist/featureOptions-docs.js +193 -0
  41. package/dist/featureOptions-docs.js.map +1 -0
  42. package/dist/featureOptions.d.ts +674 -0
  43. package/dist/featureOptions.js +870 -0
  44. package/dist/featureOptions.js.map +1 -0
  45. package/dist/ffmpeg/codecs.d.ts +256 -72
  46. package/dist/ffmpeg/codecs.js +477 -262
  47. package/dist/ffmpeg/codecs.js.map +1 -1
  48. package/dist/ffmpeg/dgram-util.d.ts +46 -0
  49. package/dist/ffmpeg/dgram-util.js +38 -0
  50. package/dist/ffmpeg/dgram-util.js.map +1 -0
  51. package/dist/ffmpeg/exec.d.ts +83 -64
  52. package/dist/ffmpeg/exec.js +77 -86
  53. package/dist/ffmpeg/exec.js.map +1 -1
  54. package/dist/ffmpeg/fmp4.d.ts +49 -2
  55. package/dist/ffmpeg/fmp4.js +47 -11
  56. package/dist/ffmpeg/fmp4.js.map +1 -1
  57. package/dist/ffmpeg/hap-enums.d.ts +214 -0
  58. package/dist/ffmpeg/hap-enums.js +92 -0
  59. package/dist/ffmpeg/hap-enums.js.map +1 -0
  60. package/dist/ffmpeg/index.d.ts +16 -9
  61. package/dist/ffmpeg/index.js +6 -0
  62. package/dist/ffmpeg/index.js.map +1 -1
  63. package/dist/ffmpeg/mp4-assembler.d.ts +120 -0
  64. package/dist/ffmpeg/mp4-assembler.js +387 -0
  65. package/dist/ffmpeg/mp4-assembler.js.map +1 -0
  66. package/dist/ffmpeg/mp4-parser.d.ts +94 -0
  67. package/dist/ffmpeg/mp4-parser.js +130 -0
  68. package/dist/ffmpeg/mp4-parser.js.map +1 -0
  69. package/dist/ffmpeg/options.d.ts +62 -149
  70. package/dist/ffmpeg/options.js +608 -499
  71. package/dist/ffmpeg/options.js.map +1 -1
  72. package/dist/ffmpeg/process.d.ts +142 -96
  73. package/dist/ffmpeg/process.js +406 -278
  74. package/dist/ffmpeg/process.js.map +1 -1
  75. package/dist/ffmpeg/record.d.ts +302 -188
  76. package/dist/ffmpeg/record.js +408 -567
  77. package/dist/ffmpeg/record.js.map +1 -1
  78. package/dist/ffmpeg/recording-process-double.d.ts +144 -0
  79. package/dist/ffmpeg/recording-process-double.js +168 -0
  80. package/dist/ffmpeg/recording-process-double.js.map +1 -0
  81. package/dist/ffmpeg/rtp-parser.d.ts +70 -0
  82. package/dist/ffmpeg/rtp-parser.js +77 -0
  83. package/dist/ffmpeg/rtp-parser.js.map +1 -0
  84. package/dist/ffmpeg/rtp.d.ts +198 -141
  85. package/dist/ffmpeg/rtp.js +473 -251
  86. package/dist/ffmpeg/rtp.js.map +1 -1
  87. package/dist/ffmpeg/settings.d.ts +5 -2
  88. package/dist/ffmpeg/settings.js +20 -5
  89. package/dist/ffmpeg/settings.js.map +1 -1
  90. package/dist/ffmpeg/stream.d.ts +57 -107
  91. package/dist/ffmpeg/stream.js +121 -150
  92. package/dist/ffmpeg/stream.js.map +1 -1
  93. package/dist/formatters.d.ts +106 -0
  94. package/dist/formatters.js +174 -0
  95. package/dist/formatters.js.map +1 -0
  96. package/dist/homebridge-enums.d.ts +30 -0
  97. package/dist/homebridge-enums.js +17 -0
  98. package/dist/homebridge-enums.js.map +1 -0
  99. package/dist/index.d.ts +12 -6
  100. package/dist/index.js +7 -2
  101. package/dist/index.js.map +1 -1
  102. package/dist/logclient/auth.d.ts +114 -0
  103. package/dist/logclient/auth.js +199 -0
  104. package/dist/logclient/auth.js.map +1 -0
  105. package/dist/logclient/cli-run.d.ts +76 -0
  106. package/dist/logclient/cli-run.js +639 -0
  107. package/dist/logclient/cli-run.js.map +1 -0
  108. package/dist/logclient/cli.d.ts +3 -0
  109. package/dist/logclient/cli.js +97 -0
  110. package/dist/logclient/cli.js.map +1 -0
  111. package/dist/logclient/client.d.ts +145 -0
  112. package/dist/logclient/client.js +600 -0
  113. package/dist/logclient/client.js.map +1 -0
  114. package/dist/logclient/config.d.ts +173 -0
  115. package/dist/logclient/config.js +199 -0
  116. package/dist/logclient/config.js.map +1 -0
  117. package/dist/logclient/endpoints.d.ts +54 -0
  118. package/dist/logclient/endpoints.js +73 -0
  119. package/dist/logclient/endpoints.js.map +1 -0
  120. package/dist/logclient/filter.d.ts +45 -0
  121. package/dist/logclient/filter.js +51 -0
  122. package/dist/logclient/filter.js.map +1 -0
  123. package/dist/logclient/frame.d.ts +93 -0
  124. package/dist/logclient/frame.js +203 -0
  125. package/dist/logclient/frame.js.map +1 -0
  126. package/dist/logclient/index.d.ts +31 -0
  127. package/dist/logclient/index.js +12 -0
  128. package/dist/logclient/index.js.map +1 -0
  129. package/dist/logclient/parser.d.ts +211 -0
  130. package/dist/logclient/parser.js +393 -0
  131. package/dist/logclient/parser.js.map +1 -0
  132. package/dist/logclient/rest.d.ts +41 -0
  133. package/dist/logclient/rest.js +111 -0
  134. package/dist/logclient/rest.js.map +1 -0
  135. package/dist/logclient/settings.d.ts +15 -0
  136. package/dist/logclient/settings.js +64 -0
  137. package/dist/logclient/settings.js.map +1 -0
  138. package/dist/logclient/socket-double.d.ts +201 -0
  139. package/dist/logclient/socket-double.js +384 -0
  140. package/dist/logclient/socket-double.js.map +1 -0
  141. package/dist/logclient/socket.d.ts +257 -0
  142. package/dist/logclient/socket.js +620 -0
  143. package/dist/logclient/socket.js.map +1 -0
  144. package/dist/logclient/stitch.d.ts +83 -0
  145. package/dist/logclient/stitch.js +146 -0
  146. package/dist/logclient/stitch.js.map +1 -0
  147. package/dist/logclient/time-expression.d.ts +42 -0
  148. package/dist/logclient/time-expression.js +181 -0
  149. package/dist/logclient/time-expression.js.map +1 -0
  150. package/dist/logclient/time-window.d.ts +38 -0
  151. package/dist/logclient/time-window.js +53 -0
  152. package/dist/logclient/time-window.js.map +1 -0
  153. package/dist/logclient/types.d.ts +107 -0
  154. package/dist/logclient/types.js +6 -0
  155. package/dist/logclient/types.js.map +1 -0
  156. package/dist/mqttClient.d.ts +287 -0
  157. package/dist/mqttClient.js +433 -0
  158. package/dist/mqttClient.js.map +1 -0
  159. package/dist/service.d.ts +64 -15
  160. package/dist/service.js +93 -66
  161. package/dist/service.js.map +1 -1
  162. package/dist/ui/featureOptions.js +870 -0
  163. package/dist/ui/featureOptions.js.map +1 -0
  164. package/dist/ui/formatters.js +174 -0
  165. package/dist/ui/formatters.js.map +1 -0
  166. package/dist/ui/pluginConfigSession.mjs +141 -0
  167. package/dist/ui/webUi-featureOptions/categoryState.mjs +135 -0
  168. package/dist/ui/webUi-featureOptions/effects/keyboard.mjs +61 -0
  169. package/dist/ui/webUi-featureOptions/effects/persist.mjs +226 -0
  170. package/dist/ui/webUi-featureOptions/effects/theme.mjs +398 -0
  171. package/dist/ui/webUi-featureOptions/effects/tokens.mjs +152 -0
  172. package/dist/ui/webUi-featureOptions/rendering.mjs +431 -0
  173. package/dist/ui/webUi-featureOptions/selectors.mjs +360 -0
  174. package/dist/ui/webUi-featureOptions/state.mjs +319 -0
  175. package/dist/ui/webUi-featureOptions/store.mjs +181 -0
  176. package/dist/ui/webUi-featureOptions/utils.mjs +200 -0
  177. package/dist/ui/webUi-featureOptions/views/connectionError.mjs +152 -0
  178. package/dist/ui/webUi-featureOptions/views/deviceInfo.mjs +80 -0
  179. package/dist/ui/webUi-featureOptions/views/header.mjs +77 -0
  180. package/dist/ui/webUi-featureOptions/views/nav.mjs +327 -0
  181. package/dist/ui/webUi-featureOptions/views/options.mjs +521 -0
  182. package/dist/ui/webUi-featureOptions/views/search.mjs +395 -0
  183. package/dist/ui/webUi-featureOptions.mjs +677 -0
  184. package/dist/ui/webUi.mjs +192 -87
  185. package/dist/util.d.ts +602 -45
  186. package/dist/util.js +783 -78
  187. package/dist/util.js.map +1 -1
  188. package/package.json +33 -15
  189. package/build/eslint-rules.mjs +0 -511
  190. package/dist/featureoptions.d.ts +0 -264
  191. package/dist/featureoptions.js +0 -480
  192. package/dist/featureoptions.js.map +0 -1
  193. package/dist/mqttclient.d.ts +0 -178
  194. package/dist/mqttclient.js +0 -310
  195. package/dist/mqttclient.js.map +0 -1
  196. package/dist/ui/featureoptions.js +0 -480
  197. package/dist/ui/featureoptions.js.map +0 -1
  198. package/dist/ui/webUi-featureoptions.mjs +0 -3765
@@ -0,0 +1,46 @@
1
+ /* Copyright(C) 2017-2026, HJD (https://github.com/hjdhjd). All rights reserved.
2
+ *
3
+ * plugin.mjs: The `@hjdhjd` ESLint plugin object - meta block plus the rule registry.
4
+ */
5
+ import packageJson from "../../package.json" with { type: "json" };
6
+ import ruleBlankAfterOpenBrace from "./rules/blank-line-after-open-brace.mjs";
7
+ import ruleCommentStyle from "./rules/comment-style.mjs";
8
+ import ruleEnforceNodeProtocol from "./rules/enforce-node-protocol.mjs";
9
+ import ruleParenComparisonsInLogical from "./rules/paren-comparisons-in-logical.mjs";
10
+ import ruleSplitTypeImports from "./rules/split-type-imports.mjs";
11
+
12
+ /**
13
+ * The `@hjdhjd` ESLint plugin object. Follows the standard ESLint plugin shape - a `meta` block identifying the plugin (used by ESLint's cache
14
+ * invalidation and tooling) plus a `rules` map keyed by the rule name that consumers reference under the `@hjdhjd/` namespace.
15
+ *
16
+ * Register the plugin in a flat config under the `plugins` property, then reference its rules by their fully qualified name:
17
+ *
18
+ * ```js
19
+ * import { plugin } from "homebridge-plugin-utils/eslint";
20
+ * export default [{
21
+ * plugins: { "@hjdhjd": plugin },
22
+ * rules: { "@hjdhjd/split-type-imports": "warn" }
23
+ * }];
24
+ * ```
25
+ *
26
+ * For a fully composed configuration that wires this plugin together with the project's TypeScript, JavaScript, and webUI rule presets, prefer the
27
+ * `config()` helper exported from `./config.mjs`.
28
+ */
29
+ const plugin = {
30
+
31
+ meta: {
32
+
33
+ name: "@hjdhjd/eslint-rules",
34
+ version: packageJson.version
35
+ },
36
+ rules: {
37
+
38
+ "blank-line-after-open-brace": ruleBlankAfterOpenBrace,
39
+ "comment-style": ruleCommentStyle,
40
+ "enforce-node-protocol": ruleEnforceNodeProtocol,
41
+ "paren-comparisons-in-logical": ruleParenComparisonsInLogical,
42
+ "split-type-imports": ruleSplitTypeImports
43
+ }
44
+ };
45
+
46
+ export default plugin;
@@ -0,0 +1,137 @@
1
+ /* Copyright(C) 2017-2026, HJD (https://github.com/hjdhjd). All rights reserved.
2
+ *
3
+ * blank-line-after-open-brace.mjs: Require a blank line after an opening brace if it is immediately followed by a newline.
4
+ */
5
+
6
+ // Find the open-brace token that needs a blank line inserted after it, or return null when the brace is canonical. The "canonical" form is one of:
7
+ // * the brace is closed on the same line (`{}` or `{ a }`), or
8
+ // * the brace is followed immediately by a blank line.
9
+ // We do not flag braces that are followed by a blank line; we only flag braces followed by content on the very next line.
10
+ function findBlankLineAfterOpenBrace(sourceCode, node) {
11
+
12
+ const openBrace = sourceCode.getFirstToken(node);
13
+ const nextToken = sourceCode.getTokenAfter(openBrace);
14
+
15
+ if(openBrace.loc.end.line >= nextToken.loc.start.line) {
16
+
17
+ return null;
18
+ }
19
+
20
+ const nextLine = sourceCode.lines[openBrace.loc.end.line];
21
+
22
+ if(nextLine.trim() === "") {
23
+
24
+ return null;
25
+ }
26
+
27
+ return openBrace;
28
+ }
29
+
30
+ // Require a blank line after an opening brace whenever the brace is followed by a newline and the next line carries non-whitespace content.
31
+ //
32
+ // Cases covered:
33
+ // * Block statements (`function`, `if`, `for`, etc.).
34
+ // * Class bodies.
35
+ // * Object expressions (including those nested as class-property initializers, which the `ObjectExpression` visitor reaches via ESLint's AST walk).
36
+ // * TypeScript interface bodies and embedded nested type literals.
37
+ // * TypeScript type alias declarations whose annotation is a type literal, and nested type literals therein.
38
+ //
39
+ // The autofix inserts a single `\n` immediately after the offending brace. Single-line braces (`{}` or `{ a }`) and braces already followed by a blank line
40
+ // are both canonical and ignored. The rule reports against the brace token itself so editor markers pinpoint the brace rather than the wider statement.
41
+ const ruleBlankAfterOpenBrace = {
42
+
43
+ create(context) {
44
+
45
+ const sourceCode = context.sourceCode;
46
+
47
+ function reportIfViolation(node) {
48
+
49
+ const openBrace = findBlankLineAfterOpenBrace(sourceCode, node);
50
+
51
+ if(openBrace === null) {
52
+
53
+ return;
54
+ }
55
+
56
+ context.report({
57
+
58
+ fix(fixer) {
59
+
60
+ return fixer.insertTextAfter(openBrace, "\n");
61
+ },
62
+ loc: openBrace.loc,
63
+ message: "Expected blank line after left brace and newline.",
64
+ node
65
+ });
66
+ }
67
+
68
+ return {
69
+
70
+ BlockStatement(node) {
71
+
72
+ reportIfViolation(node);
73
+ },
74
+
75
+ ClassBody(node) {
76
+
77
+ reportIfViolation(node);
78
+ },
79
+
80
+ // Nested object-expression property initializers inside class bodies are reached by the `ObjectExpression` visitor below, which ESLint dispatches
81
+ // automatically as it walks the AST. No manual recursion is needed here. (Compare with `TSInterfaceBody` and `TSTypeAliasDeclaration` below, which
82
+ // DO need manual recursion because `TSTypeLiteral` has no dedicated visitor in this rule.)
83
+ ObjectExpression(node) {
84
+
85
+ reportIfViolation(node);
86
+ },
87
+
88
+ TSInterfaceBody(node) {
89
+
90
+ reportIfViolation(node);
91
+
92
+ for(const property of node.body) {
93
+
94
+ if(!property.typeAnnotation || (property.type !== "TSPropertySignature") || (property.typeAnnotation.typeAnnotation.type !== "TSTypeLiteral")) {
95
+
96
+ continue;
97
+ }
98
+
99
+ reportIfViolation(property.typeAnnotation.typeAnnotation);
100
+ }
101
+ },
102
+
103
+ TSTypeAliasDeclaration(node) {
104
+
105
+ if(node.typeAnnotation.type !== "TSTypeLiteral") {
106
+
107
+ return;
108
+ }
109
+
110
+ reportIfViolation(node.typeAnnotation);
111
+
112
+ for(const member of node.typeAnnotation.members) {
113
+
114
+ if(!member.typeAnnotation || (member.type !== "TSPropertySignature") || (member.typeAnnotation.typeAnnotation.type !== "TSTypeLiteral")) {
115
+
116
+ continue;
117
+ }
118
+
119
+ reportIfViolation(member.typeAnnotation.typeAnnotation);
120
+ }
121
+ }
122
+ };
123
+ },
124
+ meta: {
125
+
126
+ docs: {
127
+
128
+ description: "require a blank line after an opening brace if it is immediately followed by a newline",
129
+ recommended: false
130
+ },
131
+ fixable: "whitespace",
132
+ schema: [],
133
+ type: "layout"
134
+ }
135
+ };
136
+
137
+ export default ruleBlankAfterOpenBrace;
@@ -0,0 +1,112 @@
1
+ /* Copyright(C) 2017-2026, HJD (https://github.com/hjdhjd). All rights reserved.
2
+ *
3
+ * blank-line-after-open-brace.test.mjs: Unit tests for the blank-line-after-open-brace rule covering each visitor (BlockStatement, ClassBody,
4
+ * ObjectExpression, TSInterfaceBody, TSTypeAliasDeclaration), the canonical single-line and already-padded forms that pass without flagging, and the
5
+ * nested TS type-literal recursion that drives the rule into the deeper levels of an interface or type-alias body.
6
+ */
7
+ import { RuleTester } from "../test-setup.mjs";
8
+ import rule from "./blank-line-after-open-brace.mjs";
9
+ import ts from "typescript-eslint";
10
+
11
+ // RuleTester wired to the typescript-eslint parser so the test suite can exercise TSInterfaceBody and TSTypeAliasDeclaration cases. The non-TS visitors
12
+ // (BlockStatement, ClassBody, ObjectExpression) parse identically under either parser, so a single tester covers every case.
13
+ const ruleTester = new RuleTester({
14
+
15
+ languageOptions: {
16
+
17
+ parser: ts.parser
18
+ }
19
+ });
20
+
21
+ ruleTester.run("blank-line-after-open-brace", rule, {
22
+
23
+ invalid: [
24
+
25
+ // BlockStatement followed immediately by content on the next line - autofix inserts a single newline after the opening brace.
26
+ {
27
+
28
+ code: "function foo() {\n return 1;\n}",
29
+ errors: [{ message: /Expected blank line after left brace/ }],
30
+ output: "function foo() {\n\n return 1;\n}"
31
+ },
32
+
33
+ // ClassBody case - same pattern. The class body's opening brace gets a blank line after it.
34
+ {
35
+
36
+ code: "class Foo {\n bar() { return 1; }\n}",
37
+ errors: 1,
38
+ output: "class Foo {\n\n bar() { return 1; }\n}"
39
+ },
40
+
41
+ // ObjectExpression case. Multi-line object literal whose first property is on the very next line.
42
+ {
43
+
44
+ code: "const x = {\n a: 1,\n b: 2\n};",
45
+ errors: 1,
46
+ output: "const x = {\n\n a: 1,\n b: 2\n};"
47
+ },
48
+
49
+ // ObjectExpression nested as a class-property initializer. The rule's header calls this case out specifically as covered via the `ObjectExpression`
50
+ // visitor's automatic AST walk. The outer class body is already padded so this test isolates the inner object's blank-line gap; only the inner brace
51
+ // fires.
52
+ {
53
+
54
+ code: "class Foo {\n\n config = {\n a: 1\n };\n}",
55
+ errors: 1,
56
+ output: "class Foo {\n\n config = {\n\n a: 1\n };\n}"
57
+ },
58
+
59
+ // TSInterfaceBody case. The opening `{` of the interface body needs the blank line.
60
+ {
61
+
62
+ code: "interface Foo {\n bar: number;\n}",
63
+ errors: 1,
64
+ output: "interface Foo {\n\n bar: number;\n}"
65
+ },
66
+
67
+ // TSTypeAliasDeclaration with a type literal annotation - the literal's opening `{` gets the blank line.
68
+ {
69
+
70
+ code: "type Foo = {\n bar: number;\n};",
71
+ errors: 1,
72
+ output: "type Foo = {\n\n bar: number;\n};"
73
+ },
74
+
75
+ // Nested type literal inside an interface property. The rule manually descends from TSInterfaceBody into TSTypeLiteral members (which have no
76
+ // dedicated visitor in this rule), so the inner brace is flagged independently of the outer interface brace.
77
+ {
78
+
79
+ code: "interface Foo {\n\n bar: {\n baz: number;\n };\n}",
80
+ errors: 1,
81
+ output: "interface Foo {\n\n bar: {\n\n baz: number;\n };\n}"
82
+ }
83
+ ],
84
+
85
+ valid: [
86
+
87
+ // Empty braces - canonical, no content to pad.
88
+ "function foo() {}",
89
+ "class Foo {}",
90
+ "const x = {};",
91
+ "interface Foo {}",
92
+ "type Foo = {};",
93
+
94
+ // Single-line braces with content - canonical, no newline after `{` so the rule doesn't engage.
95
+ "function foo() { return 1; }",
96
+ "const x = { a: 1 };",
97
+
98
+ // Multi-line braces with the canonical blank line already in place.
99
+ "function foo() {\n\n return 1;\n}",
100
+ "class Foo {\n\n bar() { return 1; }\n}",
101
+ "const x = {\n\n a: 1\n};",
102
+ "interface Foo {\n\n bar: number;\n}",
103
+ "type Foo = {\n\n bar: number;\n};",
104
+
105
+ // Nested type literal already padded.
106
+ "interface Foo {\n\n bar: {\n\n baz: number;\n };\n}",
107
+
108
+ // Type alias whose annotation is not a type literal - the rule's TSTypeAliasDeclaration visitor returns early.
109
+ "type Foo = number;",
110
+ "type Bar = string | number;"
111
+ ]
112
+ });
@@ -0,0 +1,190 @@
1
+ /* Copyright(C) 2017-2026, HJD (https://github.com/hjdhjd). All rights reserved.
2
+ *
3
+ * comment-style.mjs: Enforce ASCII-first comment style and disallow decorative banner separators.
4
+ */
5
+
6
+ // The substitution table mapping Unicode glyphs to their canonical ASCII equivalents inside comments. Extend by adding a new entry; the substitution pass
7
+ // reads this table once per character and emits the ASCII form in place.
8
+ const COMMENT_ASCII_SUBSTITUTIONS = {
9
+
10
+ "±": "+/-",
11
+ "—": "-",
12
+ "←": "<-",
13
+ "→": "->",
14
+ "↔": "<->",
15
+ "≤": "<=",
16
+ "≥": ">="
17
+ };
18
+
19
+ // A line comment whose body is exclusively a four-or-more run of `=`, `-`, or `#` characters with optional surrounding whitespace. The leading `^\s*` and
20
+ // trailing `\s*$` allow space padding (e.g., `// ==== `); the `\1{3,}` backreference forces the run to be a single repeated character.
21
+ const COMMENT_DECORATIVE_BANNER_RE = /^\s*([=\-#])\1{3,}\s*$/;
22
+
23
+ // Test whether a code point falls in the Unicode Box Drawing block (U+2500..U+257F, covering single-line, heavy, doubled, and rounded-corner forms in one
24
+ // contiguous range so a single bounds check covers every variant).
25
+ function isBoxDrawing(codePoint) {
26
+
27
+ return (codePoint >= 0x2500) && (codePoint <= 0x257F);
28
+ }
29
+
30
+ // Format a character's code point as a `U+XXXX` string for use in human-readable rule messages.
31
+ function formatCodePoint(ch) {
32
+
33
+ return "U+" + ch.codePointAt(0).toString(16).toUpperCase().padStart(4, "0");
34
+ }
35
+
36
+ // Find the source range to remove for a decorative-banner-line violation, or null when the comment is not a removable banner. A removable banner is a
37
+ // line comment (block comments use `=`/`-` runs as part of structured JSDoc syntax) whose body matches the banner regex and which is the only content on
38
+ // its source line - banners that share a line with code are left to the contents pass so removing the line does not also delete the code.
39
+ function findCommentBannerRange(sourceCode, comment) {
40
+
41
+ if(comment.type !== "Line") {
42
+
43
+ return null;
44
+ }
45
+
46
+ if(!COMMENT_DECORATIVE_BANNER_RE.test(comment.value)) {
47
+
48
+ return null;
49
+ }
50
+
51
+ const line = sourceCode.lines[comment.loc.start.line - 1];
52
+ const before = line.slice(0, comment.loc.start.column);
53
+
54
+ if(before.trim() !== "") {
55
+
56
+ return null;
57
+ }
58
+
59
+ const lineStart = sourceCode.getIndexFromLoc({ column: 0, line: comment.loc.start.line });
60
+ const nextLine = comment.loc.start.line + 1;
61
+ const lineEnd = (nextLine <= sourceCode.lines.length) ?
62
+ sourceCode.getIndexFromLoc({ column: 0, line: nextLine }) :
63
+ sourceCode.text.length;
64
+
65
+ return [ lineStart, lineEnd ];
66
+ }
67
+
68
+ // Analyze a comment's contents for substitutable Unicode glyphs and box-drawing characters. Returns null when the comment is clean; otherwise returns the
69
+ // full replacement source text plus the rule message describing the first issue found. The pass walks by code point so any future supplementary-plane
70
+ // character (e.g., emoji) is processed atomically rather than as two surrogates.
71
+ function findCommentContentsViolation(comment) {
72
+
73
+ let transformed = "";
74
+ const issues = [];
75
+
76
+ for(const ch of comment.value) {
77
+
78
+ if(ch in COMMENT_ASCII_SUBSTITUTIONS) {
79
+
80
+ transformed += COMMENT_ASCII_SUBSTITUTIONS[ch];
81
+ issues.push({ char: ch, kind: "substitution" });
82
+
83
+ continue;
84
+ }
85
+
86
+ if(isBoxDrawing(ch.codePointAt(0))) {
87
+
88
+ issues.push({ char: ch, kind: "boxdrawing" });
89
+
90
+ continue;
91
+ }
92
+
93
+ transformed += ch;
94
+ }
95
+
96
+ if(issues.length === 0) {
97
+
98
+ return null;
99
+ }
100
+
101
+ const newSource = (comment.type === "Line") ? ("//" + transformed) : ("/*" + transformed + "*/");
102
+ const first = issues[0];
103
+ const message = (first.kind === "substitution") ?
104
+ ("Replace Unicode glyph `" + first.char + "` (" + formatCodePoint(first.char) + ") with ASCII `" + COMMENT_ASCII_SUBSTITUTIONS[first.char] +
105
+ "` in comments.") :
106
+ ("Remove box-drawing character `" + first.char + "` (" + formatCodePoint(first.char) + ") from comments.");
107
+
108
+ return { message, newSource };
109
+ }
110
+
111
+ // Enforce ASCII-first comment style. The rule visits every comment in a single `Program` walk and reports the following classes of drift.
112
+ //
113
+ // Cases covered:
114
+ // * Substitutable Unicode glyphs with direct ASCII equivalents - arrows (U+2192, U+2190, U+2194), comparison operators (U+2264, U+2265), plus-minus
115
+ // (U+00B1), and the em-dash (U+2014). Each is replaced in place via the substitution table.
116
+ // * Decorative banner separators - line comments whose body is exclusively a run of four or more `=`, `-`, or `#` characters. The entire offending
117
+ // source line is removed (including the trailing newline) so the section header collapses to the line above it.
118
+ // * Characters in the Unicode Box Drawing block (U+2500..U+257F, covering single-line, heavy, doubled, and rounded-corner forms). Stripped in place.
119
+ //
120
+ // Design decisions:
121
+ // * The walk is comment-scoped by construction (via `getAllComments`), so Unicode in string literals - e.g., a webUI label that legitimately renders
122
+ // an arrow - is never touched. The rule physically cannot reach a non-comment AST node.
123
+ // * Banner removal short-circuits the contents pass: within a single walk each comment is tested for a banner first, and a match removes the line and
124
+ // `continue`s, so banner-removal and substitution never both apply to the same comment in one pass. Across ESLint's multi-pass fixing an em-dash run can
125
+ // substitute to a hyphen run that a later pass then treats as a banner and removes; that convergence to a clean section break is benign.
126
+ // * Banners that share a line with code are left alone, since removing the line would delete the code too; such cases fall through to the contents pass,
127
+ // which doesn't match because banner runs aren't substitutable.
128
+ const ruleCommentStyle = {
129
+
130
+ create(context) {
131
+
132
+ const sourceCode = context.sourceCode;
133
+
134
+ return {
135
+
136
+ Program() {
137
+
138
+ for(const comment of sourceCode.getAllComments()) {
139
+
140
+ const bannerRange = findCommentBannerRange(sourceCode, comment);
141
+
142
+ if(bannerRange !== null) {
143
+
144
+ context.report({
145
+
146
+ fix(fixer) {
147
+
148
+ return fixer.removeRange(bannerRange);
149
+ },
150
+ loc: comment.loc,
151
+ message: "Decorative banner separators are not allowed in comments. Use a plain section comment such as `// Section name.` instead."
152
+ });
153
+
154
+ continue;
155
+ }
156
+
157
+ const violation = findCommentContentsViolation(comment);
158
+
159
+ if(violation === null) {
160
+
161
+ continue;
162
+ }
163
+
164
+ context.report({
165
+
166
+ fix(fixer) {
167
+
168
+ return fixer.replaceTextRange(comment.range, violation.newSource);
169
+ },
170
+ loc: comment.loc,
171
+ message: violation.message
172
+ });
173
+ }
174
+ }
175
+ };
176
+ },
177
+ meta: {
178
+
179
+ docs: {
180
+
181
+ description: "enforce ASCII-first comment style and disallow decorative banner separators",
182
+ recommended: false
183
+ },
184
+ fixable: "code",
185
+ schema: [],
186
+ type: "suggestion"
187
+ }
188
+ };
189
+
190
+ export default ruleCommentStyle;
@@ -0,0 +1,190 @@
1
+ /* Copyright(C) 2017-2026, HJD (https://github.com/hjdhjd). All rights reserved.
2
+ *
3
+ * comment-style.test.mjs: Unit tests for the comment-style rule covering its enforcement groups (Unicode-glyph substitution, em-dash substitution,
4
+ * decorative-banner-line removal, and box-drawing-character stripping) along with the negative cases that prove comment scoping works (string literals
5
+ * are never touched).
6
+ */
7
+ import { RuleTester } from "../test-setup.mjs";
8
+ import rule from "./comment-style.mjs";
9
+
10
+ const ruleTester = new RuleTester();
11
+
12
+ ruleTester.run("comment-style", rule, {
13
+
14
+ invalid: [
15
+
16
+ // Group A: each Unicode glyph is independently replaced with its ASCII equivalent in a line comment.
17
+ {
18
+
19
+ code: "// 0 → 1 transition",
20
+ errors: [{ message: /Replace Unicode glyph/ }],
21
+ output: "// 0 -> 1 transition"
22
+ },
23
+ {
24
+
25
+ code: "// arrow ← backwards",
26
+ errors: 1,
27
+ output: "// arrow <- backwards"
28
+ },
29
+ {
30
+
31
+ code: "// bidirectional ↔ link",
32
+ errors: 1,
33
+ output: "// bidirectional <-> link"
34
+ },
35
+ {
36
+
37
+ code: "// x ≤ 10",
38
+ errors: 1,
39
+ output: "// x <= 10"
40
+ },
41
+ {
42
+
43
+ code: "// x ≥ 10",
44
+ errors: 1,
45
+ output: "// x >= 10"
46
+ },
47
+ {
48
+
49
+ code: "// tolerance ± 5",
50
+ errors: 1,
51
+ output: "// tolerance +/- 5"
52
+ },
53
+
54
+ // Group B: em-dash replacement.
55
+ {
56
+
57
+ code: "// before — after",
58
+ errors: [{ message: /U\+2014/ }],
59
+ output: "// before - after"
60
+ },
61
+
62
+ // Group A inside block comments works the same way - the rule walks all comment types, not just line comments.
63
+ {
64
+
65
+ code: "/* foo → bar */",
66
+ errors: 1,
67
+ output: "/* foo -> bar */"
68
+ },
69
+ {
70
+
71
+ code: "/* range ≤ to ≥ */",
72
+ errors: 1,
73
+ output: "/* range <= to >= */"
74
+ },
75
+
76
+ // Multiple violations in a single comment collapse into one report with one fix...autofix produces the fully cleaned text in a single pass.
77
+ {
78
+
79
+ code: "// arrow → and check ≤ ok",
80
+ errors: 1,
81
+ output: "// arrow -> and check <= ok"
82
+ },
83
+
84
+ // Group C: decorative banner separators (=, -, # runs of four or more) on a line by themselves are removed entirely along with their trailing newline.
85
+ {
86
+
87
+ code: "// Section\n// ==========\nconst x = 1;\n",
88
+ errors: [{ message: /Decorative banner separator/ }],
89
+ output: "// Section\nconst x = 1;\n"
90
+ },
91
+ {
92
+
93
+ code: "// Section\n// ----------\nconst x = 1;\n",
94
+ errors: 1,
95
+ output: "// Section\nconst x = 1;\n"
96
+ },
97
+ {
98
+
99
+ code: "// Section\n// ##########\nconst x = 1;\n",
100
+ errors: 1,
101
+ output: "// Section\nconst x = 1;\n"
102
+ },
103
+
104
+ // Banner with leading whitespace is also removed - the indentation is part of the line we strip.
105
+ {
106
+
107
+ code: "function foo() {\n // ============\n return 1;\n}\n",
108
+ errors: 1,
109
+ output: "function foo() {\n return 1;\n}\n"
110
+ },
111
+
112
+ // Banner on the final line of source (no trailing newline) is removed cleanly through end-of-file.
113
+ {
114
+
115
+ code: "const x = 1;\n// ==========",
116
+ errors: 1,
117
+ output: "const x = 1;\n"
118
+ },
119
+
120
+ // Group D: characters in the Box Drawing block (U+2500..U+257F) are stripped from comments.
121
+ {
122
+
123
+ code: "// border ─── done",
124
+ errors: [{ message: /box-drawing character/ }],
125
+ output: "// border done"
126
+ },
127
+ {
128
+
129
+ code: "// pipe │ vertical",
130
+ errors: 1,
131
+ output: "// pipe vertical"
132
+ },
133
+ {
134
+
135
+ code: "// double ═║ corner ╔",
136
+ errors: 1,
137
+ output: "// double corner "
138
+ },
139
+
140
+ // Mixed substitution and box-drawing in the same comment...one report, one fix that handles both transformations.
141
+ {
142
+
143
+ code: "// arrow → box ─ done",
144
+ errors: 1,
145
+ output: "// arrow -> box done"
146
+ },
147
+
148
+ // String literal is unaffected by the autofix; only the comment is cleaned. This is the proof that the rule is comment-scoped.
149
+ {
150
+
151
+ code: "const s = \"→\"; // arrow → string",
152
+ errors: 1,
153
+ output: "const s = \"→\"; // arrow -> string"
154
+ }
155
+ ],
156
+
157
+ valid: [
158
+
159
+ // Plain ASCII comments are unaffected.
160
+ "// This is a normal comment.",
161
+ "/* Block comment */",
162
+ "/* Multi\n * line\n * block */",
163
+
164
+ // Comments containing only ASCII punctuation including the same characters used in banners (but not as a full-line banner) are valid.
165
+ "// foo - bar",
166
+ "// 0 -> 1 transition",
167
+ "// x <= 10 and y >= 20",
168
+
169
+ // The banner regex requires the whole comment body to be a contiguous run, so a body with mixed content does not match.
170
+ "// === short label",
171
+
172
+ // Comments with banner-shaped text are allowed when they share a line with code...full-line removal would also delete the code, so we leave them be.
173
+ "const x = 1; // ====",
174
+
175
+ // Banner inside a block comment is not subject to the line-comment banner check.
176
+ "/* === */",
177
+
178
+ // Block comment using the structured `/* * */` continuation marker convention is fine - the asterisks are inside the body but not banner-shaped runs.
179
+ "/* Header.\n *\n * Body line.\n */",
180
+
181
+ // Unicode in string literals is never touched. The following cases are the load-bearing proof of the comment-only scope.
182
+ "const s = \"→\";",
183
+ "const s = \"—\";",
184
+ "const s = \"─\";",
185
+ "const arr = [ \"→\", \"≤\", \"≥\" ];",
186
+
187
+ // Em-dash inside a regular-expression literal is not a comment and is not flagged.
188
+ "const re = /—/;"
189
+ ]
190
+ });