@venizia/dev-configs 0.4.6 → 0.4.7-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 (68) hide show
  1. package/dist/checks/catalog.d.ts.map +1 -1
  2. package/dist/checks/catalog.js +4 -2
  3. package/dist/checks/catalog.js.map +1 -1
  4. package/dist/checks/purity/probe.d.ts.map +1 -1
  5. package/dist/checks/purity/probe.js +2 -1
  6. package/dist/checks/purity/probe.js.map +1 -1
  7. package/dist/checks/test-controls.d.ts.map +1 -1
  8. package/dist/checks/test-controls.js +2 -1
  9. package/dist/checks/test-controls.js.map +1 -1
  10. package/dist/cli/codemod/async-style.d.ts +41 -0
  11. package/dist/cli/codemod/async-style.d.ts.map +1 -0
  12. package/dist/cli/codemod/async-style.js +166 -0
  13. package/dist/cli/codemod/async-style.js.map +1 -0
  14. package/dist/cli/codemod/const-classes.d.ts.map +1 -1
  15. package/dist/cli/codemod/const-classes.js +2 -1
  16. package/dist/cli/codemod/const-classes.js.map +1 -1
  17. package/dist/cli/codemod/drop-redundant-casts.d.ts.map +1 -1
  18. package/dist/cli/codemod/drop-redundant-casts.js +2 -1
  19. package/dist/cli/codemod/drop-redundant-casts.js.map +1 -1
  20. package/dist/cli/codemod/files.d.ts +7 -0
  21. package/dist/cli/codemod/files.d.ts.map +1 -1
  22. package/dist/cli/codemod/files.js +33 -0
  23. package/dist/cli/codemod/files.js.map +1 -1
  24. package/dist/cli/codemod/index.d.ts.map +1 -1
  25. package/dist/cli/codemod/index.js +7 -2
  26. package/dist/cli/codemod/index.js.map +1 -1
  27. package/dist/cli/index.js +1 -0
  28. package/dist/cli/index.js.map +1 -1
  29. package/dist/linters/house/await-support.d.ts +60 -0
  30. package/dist/linters/house/await-support.d.ts.map +1 -0
  31. package/dist/linters/house/await-support.js +209 -0
  32. package/dist/linters/house/await-support.js.map +1 -0
  33. package/dist/linters/house/common.d.ts +2 -0
  34. package/dist/linters/house/common.d.ts.map +1 -1
  35. package/dist/linters/house/common.js +2 -0
  36. package/dist/linters/house/common.js.map +1 -1
  37. package/dist/linters/house/configs.d.ts +23 -0
  38. package/dist/linters/house/configs.d.ts.map +1 -1
  39. package/dist/linters/house/configs.js +46 -1
  40. package/dist/linters/house/configs.js.map +1 -1
  41. package/dist/linters/house/effect-cleanup-consistency.rule.d.ts +17 -0
  42. package/dist/linters/house/effect-cleanup-consistency.rule.d.ts.map +1 -0
  43. package/dist/linters/house/effect-cleanup-consistency.rule.js +87 -0
  44. package/dist/linters/house/effect-cleanup-consistency.rule.js.map +1 -0
  45. package/dist/linters/house/hook-support.d.ts +29 -0
  46. package/dist/linters/house/hook-support.d.ts.map +1 -0
  47. package/dist/linters/house/hook-support.js +69 -0
  48. package/dist/linters/house/hook-support.js.map +1 -0
  49. package/dist/linters/house/index.d.ts +6 -0
  50. package/dist/linters/house/index.d.ts.map +1 -1
  51. package/dist/linters/house/index.js +6 -0
  52. package/dist/linters/house/index.js.map +1 -1
  53. package/dist/linters/house/no-async-hook-callback.rule.d.ts +18 -0
  54. package/dist/linters/house/no-async-hook-callback.rule.d.ts.map +1 -0
  55. package/dist/linters/house/no-async-hook-callback.rule.js +50 -0
  56. package/dist/linters/house/no-async-hook-callback.rule.js.map +1 -0
  57. package/dist/linters/house/no-inline-await.rule.d.ts +20 -0
  58. package/dist/linters/house/no-inline-await.rule.d.ts.map +1 -0
  59. package/dist/linters/house/no-inline-await.rule.js +76 -0
  60. package/dist/linters/house/no-inline-await.rule.js.map +1 -0
  61. package/dist/linters/house/no-return-await.rule.d.ts +15 -0
  62. package/dist/linters/house/no-return-await.rule.d.ts.map +1 -0
  63. package/dist/linters/house/no-return-await.rule.js +92 -0
  64. package/dist/linters/house/no-return-await.rule.js.map +1 -0
  65. package/dist/linters/house/plugin.d.ts.map +1 -1
  66. package/dist/linters/house/plugin.js +8 -0
  67. package/dist/linters/house/plugin.js.map +1 -1
  68. package/package.json +1 -1
@@ -1,14 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.reactHouseConfigs = exports.houseConfigs = void 0;
3
+ exports.reactAsyncStyleConfigs = exports.asyncStyleConfigs = exports.reactHouseConfigs = exports.houseConfigs = void 0;
4
4
  const eslint_1 = require("../eslint");
5
5
  const jsx_files_1 = require("../jsx-files");
6
6
  const react_eslint_1 = require("../react-eslint");
7
7
  const common_1 = require("./common");
8
8
  const const_class_values_rule_1 = require("./const-class-values.rule");
9
+ const effect_cleanup_consistency_rule_1 = require("./effect-cleanup-consistency.rule");
9
10
  const max_params_own_rule_1 = require("./max-params-own.rule");
11
+ const no_async_hook_callback_rule_1 = require("./no-async-hook-callback.rule");
12
+ const no_inline_await_rule_1 = require("./no-inline-await.rule");
10
13
  const no_leaked_render_rule_1 = require("./no-leaked-render.rule");
11
14
  const no_new_error_rule_1 = require("./no-new-error.rule");
15
+ const no_return_await_rule_1 = require("./no-return-await.rule");
12
16
  const plugin_1 = require("./plugin");
13
17
  const SOURCE_FILES = ['**/*.{ts,tsx,mts,cts}'];
14
18
  const TEST_FILES = ['**/__tests__/**', '**/*.test.{ts,tsx}', '**/*.spec.{ts,tsx}'];
@@ -75,4 +79,45 @@ exports.reactHouseConfigs = [
75
79
  ...houseRuleConfigs,
76
80
  ...reactRuleConfigs,
77
81
  ];
82
+ /**
83
+ * The async style rules, opt-in until every Venizia repository has been swept (the owner's rollout: the option in
84
+ * one release, the default in the next). Spread it next to `houseConfigs`:
85
+ *
86
+ * ```js
87
+ * export default [...houseConfigs, ...asyncStyleConfigs];
88
+ * ```
89
+ *
90
+ * `await` stands alone as a statement or as the whole initializer of a declaration (`no-inline-await`), and a
91
+ * function returns the promise instead of `return await` (`no-return-await`). They apply to test files too.
92
+ */
93
+ exports.asyncStyleConfigs = [
94
+ {
95
+ files: SOURCE_FILES,
96
+ plugins: { [common_1.HOUSE_PLUGIN_NAMESPACE]: plugin_1.houseEslintPlugin },
97
+ rules: {
98
+ [no_inline_await_rule_1.NoInlineAwaitRule.NAME]: no_inline_await_rule_1.NoInlineAwaitRule.build(),
99
+ [no_return_await_rule_1.NoReturnAwaitRule.NAME]: no_return_await_rule_1.NoReturnAwaitRule.build(),
100
+ },
101
+ },
102
+ ];
103
+ /**
104
+ * The hook async rules, opt-in like `asyncStyleConfigs`. Spread it next to `reactHouseConfigs`:
105
+ *
106
+ * ```js
107
+ * export default [...reactHouseConfigs, ...reactAsyncStyleConfigs];
108
+ * ```
109
+ *
110
+ * A hook never gets an async function (`no-async-hook-callback`), and an effect callback returns a cleanup on every
111
+ * path or on none (`effect-cleanup-consistency`). Hooks live in `.ts` files too, so it covers all source files.
112
+ */
113
+ exports.reactAsyncStyleConfigs = [
114
+ {
115
+ files: SOURCE_FILES,
116
+ plugins: { [common_1.HOUSE_PLUGIN_NAMESPACE]: plugin_1.houseEslintPlugin },
117
+ rules: {
118
+ [no_async_hook_callback_rule_1.NoAsyncHookCallbackRule.NAME]: no_async_hook_callback_rule_1.NoAsyncHookCallbackRule.build(),
119
+ [effect_cleanup_consistency_rule_1.EffectCleanupConsistencyRule.NAME]: effect_cleanup_consistency_rule_1.EffectCleanupConsistencyRule.build(),
120
+ },
121
+ },
122
+ ];
78
123
  //# sourceMappingURL=configs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"configs.js","sourceRoot":"","sources":["../../../src/linters/house/configs.ts"],"names":[],"mappings":";;;AACA,sCAAoD;AACpD,4CAAyC;AACzC,kDAA+D;AAC/D,qCAAkD;AAClD,uEAAiE;AACjE,+DAAyD;AACzD,mEAA6D;AAC7D,2DAAqD;AACrD,qCAA6C;AAE7C,MAAM,YAAY,GAAG,CAAC,uBAAuB,CAAC,CAAC;AAC/C,MAAM,UAAU,GAAG,CAAC,iBAAiB,EAAE,oBAAoB,EAAE,oBAAoB,CAAC,CAAC;AAEnF;;;GAGG;AACH,MAAM,YAAY,GAAG,+CAA+C,CAAC;AAErE;;;;;;;;GAQG;AACH,MAAM,gBAAgB,GAAoB;IACxC;QACE,KAAK,EAAE,YAAY;QACnB,OAAO,EAAE,EAAE,CAAC,+BAAsB,CAAC,EAAE,0BAAiB,EAAE;QACxD,KAAK,EAAE;YACL,CAAC,sCAAgB,CAAC,IAAI,CAAC,EAAE,sCAAgB,CAAC,KAAK,EAAE;YACjD,CAAC,kCAAc,CAAC,IAAI,CAAC,EAAE,kCAAc,CAAC,KAAK,EAAE;YAC7C,CAAC,8CAAoB,CAAC,IAAI,CAAC,EAAE,8CAAoB,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;YAC7E,CAAC,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC;SACtD;KACF;IACD;QACE,KAAK,EAAE,UAAU;QACjB,KAAK,EAAE;YACL,CAAC,kCAAc,CAAC,IAAI,CAAC,EAAE,KAAK;YAC5B,CAAC,8CAAoB,CAAC,IAAI,CAAC,EAAE,KAAK;YAClC,CAAC,YAAY,CAAC,EAAE,KAAK;SACtB;KACF;CACF,CAAC;AAEF;;;;;;;;;;GAUG;AACU,QAAA,YAAY,GAAoB,CAAC,GAAG,gCAAuB,EAAE,GAAG,gBAAgB,CAAC,CAAC;AAE/F;;;GAGG;AACH,MAAM,gBAAgB,GAAoB;IACxC;QACE,KAAK,EAAE,qBAAS;QAChB,OAAO,EAAE,EAAE,CAAC,+BAAsB,CAAC,EAAE,0BAAiB,EAAE;QACxD,KAAK,EAAE,EAAE,CAAC,0CAAkB,CAAC,IAAI,CAAC,EAAE,0CAAkB,CAAC,KAAK,EAAE,EAAE;KACjE;CACF,CAAC;AAEF,kHAAkH;AACrG,QAAA,iBAAiB,GAAoB;IAChD,GAAG,2CAA4B;IAC/B,GAAG,gBAAgB;IACnB,GAAG,gBAAgB;CACpB,CAAC","sourcesContent":["import type { Linter } from 'eslint';\nimport { namingConventionConfigs } from '../eslint';\nimport { JSX_FILES } from '../jsx-files';\nimport { reactNamingConventionConfigs } from '../react-eslint';\nimport { HOUSE_PLUGIN_NAMESPACE } from './common';\nimport { ConstClassValuesRule } from './const-class-values.rule';\nimport { MaxParamsOwnRule } from './max-params-own.rule';\nimport { NoLeakedRenderRule } from './no-leaked-render.rule';\nimport { NoNewErrorRule } from './no-new-error.rule';\nimport { houseEslintPlugin } from './plugin';\n\nconst SOURCE_FILES = ['**/*.{ts,tsx,mts,cts}'];\nconst TEST_FILES = ['**/__tests__/**', '**/*.test.{ts,tsx}', '**/*.spec.{ts,tsx}'];\n\n/**\n * V-C-10: no explicit cast. `as const` is always allowed; `satisfies`, a generic or a narrower type\n * replaces the rest.\n */\nconst NO_CAST_RULE = '@typescript-eslint/consistent-type-assertions';\n\n/**\n * The house rules: at most 5 positional parameters on a function we write, and errors thrown through\n * `getError` only. Tests are exempt from `no-new-error`: a test throws a plain `Error` on purpose, to\n * stand in for what a driver or a third-party library throws.\n *\n * Two rules only warn, so a project adopts them without failing its lint: `const-class-values` (V-C-08, a\n * string-literal union in an exported declaration) and `consistent-type-assertions` (V-C-10, an `as X`\n * cast). Tests are exempt from both: a test stubs what it does not build.\n */\nconst houseRuleConfigs: Linter.Config[] = [\n {\n files: SOURCE_FILES,\n plugins: { [HOUSE_PLUGIN_NAMESPACE]: houseEslintPlugin },\n rules: {\n [MaxParamsOwnRule.NAME]: MaxParamsOwnRule.build(),\n [NoNewErrorRule.NAME]: NoNewErrorRule.build(),\n [ConstClassValuesRule.NAME]: ConstClassValuesRule.build({ severity: 'warn' }),\n [NO_CAST_RULE]: ['warn', { assertionStyle: 'never' }],\n },\n },\n {\n files: TEST_FILES,\n rules: {\n [NoNewErrorRule.NAME]: 'off',\n [ConstClassValuesRule.NAME]: 'off',\n [NO_CAST_RULE]: 'off',\n },\n },\n];\n\n/**\n * The ESLint side of a project that lints with oxlint: the naming convention plus the house rules -\n * everything oxlint cannot express. Run it next to `oxlintConfigs`.\n *\n * ```js\n * // .configs/lints/eslint.config.mjs\n * import { houseConfigs } from '@venizia/dev-configs/linters';\n *\n * export default houseConfigs;\n * ```\n */\nexport const houseConfigs: Linter.Config[] = [...namingConventionConfigs, ...houseRuleConfigs];\n\n/**\n * The React house rule: a JSX child is drawn with `cond ? <X /> : null`, never `cond && <X />`, which\n * draws `0` when `cond` is `0`.\n */\nconst reactRuleConfigs: Linter.Config[] = [\n {\n files: JSX_FILES,\n plugins: { [HOUSE_PLUGIN_NAMESPACE]: houseEslintPlugin },\n rules: { [NoLeakedRenderRule.NAME]: NoLeakedRenderRule.build() },\n },\n];\n\n/** `houseConfigs` for a React project: `reactNamingConventionConfigs`, the house rules and `no-leaked-render`. */\nexport const reactHouseConfigs: Linter.Config[] = [\n ...reactNamingConventionConfigs,\n ...houseRuleConfigs,\n ...reactRuleConfigs,\n];\n"]}
1
+ {"version":3,"file":"configs.js","sourceRoot":"","sources":["../../../src/linters/house/configs.ts"],"names":[],"mappings":";;;AACA,sCAAoD;AACpD,4CAAyC;AACzC,kDAA+D;AAC/D,qCAAkD;AAClD,uEAAiE;AACjE,uFAAiF;AACjF,+DAAyD;AACzD,+EAAwE;AACxE,iEAA2D;AAC3D,mEAA6D;AAC7D,2DAAqD;AACrD,iEAA2D;AAC3D,qCAA6C;AAE7C,MAAM,YAAY,GAAG,CAAC,uBAAuB,CAAC,CAAC;AAC/C,MAAM,UAAU,GAAG,CAAC,iBAAiB,EAAE,oBAAoB,EAAE,oBAAoB,CAAC,CAAC;AAEnF;;;GAGG;AACH,MAAM,YAAY,GAAG,+CAA+C,CAAC;AAErE;;;;;;;;GAQG;AACH,MAAM,gBAAgB,GAAoB;IACxC;QACE,KAAK,EAAE,YAAY;QACnB,OAAO,EAAE,EAAE,CAAC,+BAAsB,CAAC,EAAE,0BAAiB,EAAE;QACxD,KAAK,EAAE;YACL,CAAC,sCAAgB,CAAC,IAAI,CAAC,EAAE,sCAAgB,CAAC,KAAK,EAAE;YACjD,CAAC,kCAAc,CAAC,IAAI,CAAC,EAAE,kCAAc,CAAC,KAAK,EAAE;YAC7C,CAAC,8CAAoB,CAAC,IAAI,CAAC,EAAE,8CAAoB,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;YAC7E,CAAC,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC;SACtD;KACF;IACD;QACE,KAAK,EAAE,UAAU;QACjB,KAAK,EAAE;YACL,CAAC,kCAAc,CAAC,IAAI,CAAC,EAAE,KAAK;YAC5B,CAAC,8CAAoB,CAAC,IAAI,CAAC,EAAE,KAAK;YAClC,CAAC,YAAY,CAAC,EAAE,KAAK;SACtB;KACF;CACF,CAAC;AAEF;;;;;;;;;;GAUG;AACU,QAAA,YAAY,GAAoB,CAAC,GAAG,gCAAuB,EAAE,GAAG,gBAAgB,CAAC,CAAC;AAE/F;;;GAGG;AACH,MAAM,gBAAgB,GAAoB;IACxC;QACE,KAAK,EAAE,qBAAS;QAChB,OAAO,EAAE,EAAE,CAAC,+BAAsB,CAAC,EAAE,0BAAiB,EAAE;QACxD,KAAK,EAAE,EAAE,CAAC,0CAAkB,CAAC,IAAI,CAAC,EAAE,0CAAkB,CAAC,KAAK,EAAE,EAAE;KACjE;CACF,CAAC;AAEF,kHAAkH;AACrG,QAAA,iBAAiB,GAAoB;IAChD,GAAG,2CAA4B;IAC/B,GAAG,gBAAgB;IACnB,GAAG,gBAAgB;CACpB,CAAC;AAEF;;;;;;;;;;GAUG;AACU,QAAA,iBAAiB,GAAoB;IAChD;QACE,KAAK,EAAE,YAAY;QACnB,OAAO,EAAE,EAAE,CAAC,+BAAsB,CAAC,EAAE,0BAAiB,EAAE;QACxD,KAAK,EAAE;YACL,CAAC,wCAAiB,CAAC,IAAI,CAAC,EAAE,wCAAiB,CAAC,KAAK,EAAE;YACnD,CAAC,wCAAiB,CAAC,IAAI,CAAC,EAAE,wCAAiB,CAAC,KAAK,EAAE;SACpD;KACF;CACF,CAAC;AAEF;;;;;;;;;GASG;AACU,QAAA,sBAAsB,GAAoB;IACrD;QACE,KAAK,EAAE,YAAY;QACnB,OAAO,EAAE,EAAE,CAAC,+BAAsB,CAAC,EAAE,0BAAiB,EAAE;QACxD,KAAK,EAAE;YACL,CAAC,qDAAuB,CAAC,IAAI,CAAC,EAAE,qDAAuB,CAAC,KAAK,EAAE;YAC/D,CAAC,8DAA4B,CAAC,IAAI,CAAC,EAAE,8DAA4B,CAAC,KAAK,EAAE;SAC1E;KACF;CACF,CAAC","sourcesContent":["import type { Linter } from 'eslint';\nimport { namingConventionConfigs } from '../eslint';\nimport { JSX_FILES } from '../jsx-files';\nimport { reactNamingConventionConfigs } from '../react-eslint';\nimport { HOUSE_PLUGIN_NAMESPACE } from './common';\nimport { ConstClassValuesRule } from './const-class-values.rule';\nimport { EffectCleanupConsistencyRule } from './effect-cleanup-consistency.rule';\nimport { MaxParamsOwnRule } from './max-params-own.rule';\nimport { NoAsyncHookCallbackRule } from './no-async-hook-callback.rule';\nimport { NoInlineAwaitRule } from './no-inline-await.rule';\nimport { NoLeakedRenderRule } from './no-leaked-render.rule';\nimport { NoNewErrorRule } from './no-new-error.rule';\nimport { NoReturnAwaitRule } from './no-return-await.rule';\nimport { houseEslintPlugin } from './plugin';\n\nconst SOURCE_FILES = ['**/*.{ts,tsx,mts,cts}'];\nconst TEST_FILES = ['**/__tests__/**', '**/*.test.{ts,tsx}', '**/*.spec.{ts,tsx}'];\n\n/**\n * V-C-10: no explicit cast. `as const` is always allowed; `satisfies`, a generic or a narrower type\n * replaces the rest.\n */\nconst NO_CAST_RULE = '@typescript-eslint/consistent-type-assertions';\n\n/**\n * The house rules: at most 5 positional parameters on a function we write, and errors thrown through\n * `getError` only. Tests are exempt from `no-new-error`: a test throws a plain `Error` on purpose, to\n * stand in for what a driver or a third-party library throws.\n *\n * Two rules only warn, so a project adopts them without failing its lint: `const-class-values` (V-C-08, a\n * string-literal union in an exported declaration) and `consistent-type-assertions` (V-C-10, an `as X`\n * cast). Tests are exempt from both: a test stubs what it does not build.\n */\nconst houseRuleConfigs: Linter.Config[] = [\n {\n files: SOURCE_FILES,\n plugins: { [HOUSE_PLUGIN_NAMESPACE]: houseEslintPlugin },\n rules: {\n [MaxParamsOwnRule.NAME]: MaxParamsOwnRule.build(),\n [NoNewErrorRule.NAME]: NoNewErrorRule.build(),\n [ConstClassValuesRule.NAME]: ConstClassValuesRule.build({ severity: 'warn' }),\n [NO_CAST_RULE]: ['warn', { assertionStyle: 'never' }],\n },\n },\n {\n files: TEST_FILES,\n rules: {\n [NoNewErrorRule.NAME]: 'off',\n [ConstClassValuesRule.NAME]: 'off',\n [NO_CAST_RULE]: 'off',\n },\n },\n];\n\n/**\n * The ESLint side of a project that lints with oxlint: the naming convention plus the house rules -\n * everything oxlint cannot express. Run it next to `oxlintConfigs`.\n *\n * ```js\n * // .configs/lints/eslint.config.mjs\n * import { houseConfigs } from '@venizia/dev-configs/linters';\n *\n * export default houseConfigs;\n * ```\n */\nexport const houseConfigs: Linter.Config[] = [...namingConventionConfigs, ...houseRuleConfigs];\n\n/**\n * The React house rule: a JSX child is drawn with `cond ? <X /> : null`, never `cond && <X />`, which\n * draws `0` when `cond` is `0`.\n */\nconst reactRuleConfigs: Linter.Config[] = [\n {\n files: JSX_FILES,\n plugins: { [HOUSE_PLUGIN_NAMESPACE]: houseEslintPlugin },\n rules: { [NoLeakedRenderRule.NAME]: NoLeakedRenderRule.build() },\n },\n];\n\n/** `houseConfigs` for a React project: `reactNamingConventionConfigs`, the house rules and `no-leaked-render`. */\nexport const reactHouseConfigs: Linter.Config[] = [\n ...reactNamingConventionConfigs,\n ...houseRuleConfigs,\n ...reactRuleConfigs,\n];\n\n/**\n * The async style rules, opt-in until every Venizia repository has been swept (the owner's rollout: the option in\n * one release, the default in the next). Spread it next to `houseConfigs`:\n *\n * ```js\n * export default [...houseConfigs, ...asyncStyleConfigs];\n * ```\n *\n * `await` stands alone as a statement or as the whole initializer of a declaration (`no-inline-await`), and a\n * function returns the promise instead of `return await` (`no-return-await`). They apply to test files too.\n */\nexport const asyncStyleConfigs: Linter.Config[] = [\n {\n files: SOURCE_FILES,\n plugins: { [HOUSE_PLUGIN_NAMESPACE]: houseEslintPlugin },\n rules: {\n [NoInlineAwaitRule.NAME]: NoInlineAwaitRule.build(),\n [NoReturnAwaitRule.NAME]: NoReturnAwaitRule.build(),\n },\n },\n];\n\n/**\n * The hook async rules, opt-in like `asyncStyleConfigs`. Spread it next to `reactHouseConfigs`:\n *\n * ```js\n * export default [...reactHouseConfigs, ...reactAsyncStyleConfigs];\n * ```\n *\n * A hook never gets an async function (`no-async-hook-callback`), and an effect callback returns a cleanup on every\n * path or on none (`effect-cleanup-consistency`). Hooks live in `.ts` files too, so it covers all source files.\n */\nexport const reactAsyncStyleConfigs: Linter.Config[] = [\n {\n files: SOURCE_FILES,\n plugins: { [HOUSE_PLUGIN_NAMESPACE]: houseEslintPlugin },\n rules: {\n [NoAsyncHookCallbackRule.NAME]: NoAsyncHookCallbackRule.build(),\n [EffectCleanupConsistencyRule.NAME]: EffectCleanupConsistencyRule.build(),\n },\n },\n];\n"]}
@@ -0,0 +1,17 @@
1
+ import type { Rule } from 'eslint';
2
+ import type { TRuleSeverity } from '../rules/common';
3
+ /**
4
+ * `venizia/effect-cleanup-consistency`. The callback of `useEffect`, `useLayoutEffect` and `useInsertionEffect`
5
+ * returns a cleanup on every path or on none. An early `return;` beside a later `return () => {}` leaves React
6
+ * with a cleanup on some runs only; the early exit returns a no-op instead. A bare `return;` and falling off the
7
+ * end both mean "no value". It uses ESLint's code path analysis the way `consistent-return` does, so an
8
+ * unreachable end of the function is not a path. A nested function's own `return` is not counted.
9
+ */
10
+ export declare class EffectCleanupConsistencyRule {
11
+ static readonly NAME = "venizia/effect-cleanup-consistency";
12
+ static readonly RULE: Rule.RuleModule;
13
+ static build(opts?: {
14
+ severity?: TRuleSeverity;
15
+ }): TRuleSeverity;
16
+ }
17
+ //# sourceMappingURL=effect-cleanup-consistency.rule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"effect-cleanup-consistency.rule.d.ts","sourceRoot":"","sources":["../../../src/linters/house/effect-cleanup-consistency.rule.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAarD;;;;;;GAMG;AACH,qBAAa,4BAA4B;IACvC,MAAM,CAAC,QAAQ,CAAC,IAAI,wCAA0D;IAE9E,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,CA2EnC;IAEF,MAAM,CAAC,KAAK,CAAC,IAAI,GAAE;QAAE,QAAQ,CAAC,EAAE,aAAa,CAAA;KAAO,GAAG,aAAa;CAGrE"}
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EffectCleanupConsistencyRule = void 0;
4
+ const common_1 = require("./common");
5
+ const hook_support_1 = require("./hook-support");
6
+ /**
7
+ * `venizia/effect-cleanup-consistency`. The callback of `useEffect`, `useLayoutEffect` and `useInsertionEffect`
8
+ * returns a cleanup on every path or on none. An early `return;` beside a later `return () => {}` leaves React
9
+ * with a cleanup on some runs only; the early exit returns a no-op instead. A bare `return;` and falling off the
10
+ * end both mean "no value". It uses ESLint's code path analysis the way `consistent-return` does, so an
11
+ * unreachable end of the function is not a path. A nested function's own `return` is not counted.
12
+ */
13
+ class EffectCleanupConsistencyRule {
14
+ static { this.NAME = `${common_1.HOUSE_PLUGIN_NAMESPACE}/effect-cleanup-consistency`; }
15
+ static { this.RULE = {
16
+ meta: {
17
+ type: 'problem',
18
+ schema: [],
19
+ messages: {
20
+ inconsistent: 'The callback of `{{hook}}` returns a cleanup on some paths only; return one on every path (a no-op `() => {}` for an early exit) or on none.',
21
+ },
22
+ },
23
+ create: (context) => {
24
+ const targets = new Map();
25
+ let frame;
26
+ const checkExit = (node) => {
27
+ const current = frame;
28
+ if (!current?.hook ||
29
+ (0, common_1.nodeType)({ node: (0, common_1.readField)({ node, key: 'body' }) }) !== 'BlockStatement') {
30
+ return;
31
+ }
32
+ const endIsReachable = [...current.segments].some((segment) => segment.reachable);
33
+ if (current.returnsValue && (current.returnsNothing || endIsReachable)) {
34
+ context.report({ node, messageId: 'inconsistent', data: { hook: current.hook } });
35
+ }
36
+ };
37
+ return {
38
+ [common_1.AstNodeTypes.CALL_EXPRESSION]: (node) => {
39
+ const hook = hook_support_1.HookSupport.calleeName({ call: node });
40
+ const args = (0, common_1.readField)({ node, key: 'arguments' });
41
+ if (!hook_support_1.HookSupport.isEffectHook({ name: hook }) ||
42
+ hook === undefined ||
43
+ !Array.isArray(args)) {
44
+ return;
45
+ }
46
+ if (hook_support_1.HookSupport.isCallback({ node: args[0] })) {
47
+ targets.set(args[0], hook);
48
+ }
49
+ },
50
+ onCodePathStart: (_codePath, node) => {
51
+ frame = {
52
+ upper: frame,
53
+ hook: targets.get(node),
54
+ returnsValue: false,
55
+ returnsNothing: false,
56
+ segments: new Set(),
57
+ };
58
+ },
59
+ onCodePathEnd: () => {
60
+ frame = frame?.upper;
61
+ },
62
+ onCodePathSegmentStart: (segment) => frame?.segments.add(segment),
63
+ onCodePathSegmentEnd: (segment) => frame?.segments.delete(segment),
64
+ onUnreachableCodePathSegmentStart: (segment) => frame?.segments.add(segment),
65
+ onUnreachableCodePathSegmentEnd: (segment) => frame?.segments.delete(segment),
66
+ [common_1.AstNodeTypes.RETURN_STATEMENT]: (node) => {
67
+ if (!frame) {
68
+ return;
69
+ }
70
+ if ((0, common_1.readField)({ node, key: 'argument' })) {
71
+ frame.returnsValue = true;
72
+ }
73
+ else {
74
+ frame.returnsNothing = true;
75
+ }
76
+ },
77
+ 'FunctionExpression:exit': checkExit,
78
+ 'ArrowFunctionExpression:exit': checkExit,
79
+ };
80
+ },
81
+ }; }
82
+ static build(opts = {}) {
83
+ return opts.severity ?? 'error';
84
+ }
85
+ }
86
+ exports.EffectCleanupConsistencyRule = EffectCleanupConsistencyRule;
87
+ //# sourceMappingURL=effect-cleanup-consistency.rule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"effect-cleanup-consistency.rule.js","sourceRoot":"","sources":["../../../src/linters/house/effect-cleanup-consistency.rule.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Rule } from 'eslint';\nimport type { TRuleSeverity } from '../rules/common';\nimport { AstNodeTypes, HOUSE_PLUGIN_NAMESPACE, nodeType, readField } from './common';\nimport { HookSupport } from './hook-support';\n\n/** What the rule knows about the function whose code path is open; one per function, so a nested function is its own. */\ninterface IFunctionFrame {\n upper: IFunctionFrame | undefined;\n hook: string | undefined;\n returnsValue: boolean;\n returnsNothing: boolean;\n segments: Set<{ reachable: boolean }>;\n}\n\n/**\n * `venizia/effect-cleanup-consistency`. The callback of `useEffect`, `useLayoutEffect` and `useInsertionEffect`\n * returns a cleanup on every path or on none. An early `return;` beside a later `return () => {}` leaves React\n * with a cleanup on some runs only; the early exit returns a no-op instead. A bare `return;` and falling off the\n * end both mean \"no value\". It uses ESLint's code path analysis the way `consistent-return` does, so an\n * unreachable end of the function is not a path. A nested function's own `return` is not counted.\n */\nexport class EffectCleanupConsistencyRule {\n static readonly NAME = `${HOUSE_PLUGIN_NAMESPACE}/effect-cleanup-consistency`;\n\n static readonly RULE: Rule.RuleModule = {\n meta: {\n type: 'problem',\n schema: [],\n messages: {\n inconsistent:\n 'The callback of `{{hook}}` returns a cleanup on some paths only; return one on every path (a no-op `() => {}` for an early exit) or on none.',\n },\n },\n create: (context) => {\n const targets = new Map<unknown, string>();\n let frame: IFunctionFrame | undefined;\n\n const checkExit = (node: Rule.Node) => {\n const current = frame;\n if (\n !current?.hook ||\n nodeType({ node: readField({ node, key: 'body' }) }) !== 'BlockStatement'\n ) {\n return;\n }\n\n const endIsReachable = [...current.segments].some((segment) => segment.reachable);\n if (current.returnsValue && (current.returnsNothing || endIsReachable)) {\n context.report({ node, messageId: 'inconsistent', data: { hook: current.hook } });\n }\n };\n\n return {\n [AstNodeTypes.CALL_EXPRESSION]: (node: Rule.Node) => {\n const hook = HookSupport.calleeName({ call: node });\n const args = readField({ node, key: 'arguments' });\n if (\n !HookSupport.isEffectHook({ name: hook }) ||\n hook === undefined ||\n !Array.isArray(args)\n ) {\n return;\n }\n\n if (HookSupport.isCallback({ node: args[0] })) {\n targets.set(args[0], hook);\n }\n },\n onCodePathStart: (_codePath, node) => {\n frame = {\n upper: frame,\n hook: targets.get(node),\n returnsValue: false,\n returnsNothing: false,\n segments: new Set(),\n };\n },\n onCodePathEnd: () => {\n frame = frame?.upper;\n },\n onCodePathSegmentStart: (segment) => frame?.segments.add(segment),\n onCodePathSegmentEnd: (segment) => frame?.segments.delete(segment),\n onUnreachableCodePathSegmentStart: (segment) => frame?.segments.add(segment),\n onUnreachableCodePathSegmentEnd: (segment) => frame?.segments.delete(segment),\n [AstNodeTypes.RETURN_STATEMENT]: (node: Rule.Node) => {\n if (!frame) {\n return;\n }\n\n if (readField({ node, key: 'argument' })) {\n frame.returnsValue = true;\n } else {\n frame.returnsNothing = true;\n }\n },\n 'FunctionExpression:exit': checkExit,\n 'ArrowFunctionExpression:exit': checkExit,\n };\n },\n };\n\n static build(opts: { severity?: TRuleSeverity } = {}): TRuleSeverity {\n return opts.severity ?? 'error';\n }\n}\n"]}
@@ -0,0 +1,29 @@
1
+ /** The shared parts of the two hook rules: which call is a hook, and which argument is a function. */
2
+ export declare class HookSupport {
3
+ /** `useX(...)` and `React.useX(...)`: the name the call is made through, or `undefined` when it is not a plain name. */
4
+ static calleeName(opts: {
5
+ call: unknown;
6
+ }): string | undefined;
7
+ static isHook(opts: {
8
+ name: string | undefined;
9
+ }): boolean;
10
+ static isEffectHook(opts: {
11
+ name: string | undefined;
12
+ }): boolean;
13
+ /** An arrow or a function expression: the two forms a hook callback is written in. */
14
+ static isCallback(opts: {
15
+ node: unknown;
16
+ }): boolean;
17
+ static isAsync(opts: {
18
+ node: unknown;
19
+ }): boolean;
20
+ /**
21
+ * The async functions an argument hands over: the argument itself, a property value of an object literal
22
+ * (`useMutation({ mutationFn: async () => {} })`, an async method too) and an element of an array literal, at any
23
+ * depth of literals. It does not look inside a function: what a sync callback builds is not an argument.
24
+ */
25
+ static asyncFunctionsIn(opts: {
26
+ node: unknown;
27
+ }): unknown[];
28
+ }
29
+ //# sourceMappingURL=hook-support.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-support.d.ts","sourceRoot":"","sources":["../../../src/linters/house/hook-support.ts"],"names":[],"mappings":"AAMA,sGAAsG;AACtG,qBAAa,WAAW;IACtB,wHAAwH;IACxH,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,GAAG,MAAM,GAAG,SAAS;IAoB9D,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,GAAG,OAAO;IAI1D,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,GAAG,OAAO;IAIhE,sFAAsF;IACtF,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO;IAInD,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO;IAIhD;;;;OAIG;IACH,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,EAAE;CA8B5D"}
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HookSupport = void 0;
4
+ const common_1 = require("./common");
5
+ const HOOK_NAME = /^use[A-Z]/;
6
+ const EFFECT_HOOKS = new Set(['useEffect', 'useLayoutEffect', 'useInsertionEffect']);
7
+ const CALLBACK_TYPES = new Set(['ArrowFunctionExpression', 'FunctionExpression']);
8
+ /** The shared parts of the two hook rules: which call is a hook, and which argument is a function. */
9
+ class HookSupport {
10
+ /** `useX(...)` and `React.useX(...)`: the name the call is made through, or `undefined` when it is not a plain name. */
11
+ static calleeName(opts) {
12
+ const callee = (0, common_1.readField)({ node: opts.call, key: 'callee' });
13
+ const type = (0, common_1.nodeType)({ node: callee });
14
+ if (type === 'Identifier') {
15
+ const name = (0, common_1.readField)({ node: callee, key: 'name' });
16
+ return typeof name === 'string' ? name : undefined;
17
+ }
18
+ if (type === 'MemberExpression' && (0, common_1.readField)({ node: callee, key: 'computed' }) === false) {
19
+ const property = (0, common_1.readField)({ node: callee, key: 'property' });
20
+ const name = (0, common_1.readField)({ node: property, key: 'name' });
21
+ return (0, common_1.nodeType)({ node: property }) === 'Identifier' && typeof name === 'string'
22
+ ? name
23
+ : undefined;
24
+ }
25
+ return undefined;
26
+ }
27
+ static isHook(opts) {
28
+ return opts.name !== undefined && HOOK_NAME.test(opts.name);
29
+ }
30
+ static isEffectHook(opts) {
31
+ return opts.name !== undefined && EFFECT_HOOKS.has(opts.name);
32
+ }
33
+ /** An arrow or a function expression: the two forms a hook callback is written in. */
34
+ static isCallback(opts) {
35
+ return CALLBACK_TYPES.has((0, common_1.nodeType)({ node: opts.node }) ?? '');
36
+ }
37
+ static isAsync(opts) {
38
+ return HookSupport.isCallback(opts) && (0, common_1.readField)({ node: opts.node, key: 'async' }) === true;
39
+ }
40
+ /**
41
+ * The async functions an argument hands over: the argument itself, a property value of an object literal
42
+ * (`useMutation({ mutationFn: async () => {} })`, an async method too) and an element of an array literal, at any
43
+ * depth of literals. It does not look inside a function: what a sync callback builds is not an argument.
44
+ */
45
+ static asyncFunctionsIn(opts) {
46
+ const { node } = opts;
47
+ const type = (0, common_1.nodeType)({ node });
48
+ if (HookSupport.isAsync({ node })) {
49
+ return [node];
50
+ }
51
+ if (type === 'ObjectExpression') {
52
+ const properties = (0, common_1.readField)({ node, key: 'properties' });
53
+ return Array.isArray(properties)
54
+ ? properties.flatMap((property) => (0, common_1.nodeType)({ node: property }) === 'Property'
55
+ ? HookSupport.asyncFunctionsIn({ node: (0, common_1.readField)({ node: property, key: 'value' }) })
56
+ : [])
57
+ : [];
58
+ }
59
+ if (type === 'ArrayExpression') {
60
+ const elements = (0, common_1.readField)({ node, key: 'elements' });
61
+ return Array.isArray(elements)
62
+ ? elements.flatMap((element) => HookSupport.asyncFunctionsIn({ node: element }))
63
+ : [];
64
+ }
65
+ return [];
66
+ }
67
+ }
68
+ exports.HookSupport = HookSupport;
69
+ //# sourceMappingURL=hook-support.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-support.js","sourceRoot":"","sources":["../../../src/linters/house/hook-support.ts"],"names":[],"mappings":"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","sourcesContent":["import { nodeType, readField } from './common';\n\nconst HOOK_NAME = /^use[A-Z]/;\nconst EFFECT_HOOKS = new Set(['useEffect', 'useLayoutEffect', 'useInsertionEffect']);\nconst CALLBACK_TYPES = new Set(['ArrowFunctionExpression', 'FunctionExpression']);\n\n/** The shared parts of the two hook rules: which call is a hook, and which argument is a function. */\nexport class HookSupport {\n /** `useX(...)` and `React.useX(...)`: the name the call is made through, or `undefined` when it is not a plain name. */\n static calleeName(opts: { call: unknown }): string | undefined {\n const callee = readField({ node: opts.call, key: 'callee' });\n const type = nodeType({ node: callee });\n\n if (type === 'Identifier') {\n const name = readField({ node: callee, key: 'name' });\n return typeof name === 'string' ? name : undefined;\n }\n\n if (type === 'MemberExpression' && readField({ node: callee, key: 'computed' }) === false) {\n const property = readField({ node: callee, key: 'property' });\n const name = readField({ node: property, key: 'name' });\n return nodeType({ node: property }) === 'Identifier' && typeof name === 'string'\n ? name\n : undefined;\n }\n\n return undefined;\n }\n\n static isHook(opts: { name: string | undefined }): boolean {\n return opts.name !== undefined && HOOK_NAME.test(opts.name);\n }\n\n static isEffectHook(opts: { name: string | undefined }): boolean {\n return opts.name !== undefined && EFFECT_HOOKS.has(opts.name);\n }\n\n /** An arrow or a function expression: the two forms a hook callback is written in. */\n static isCallback(opts: { node: unknown }): boolean {\n return CALLBACK_TYPES.has(nodeType({ node: opts.node }) ?? '');\n }\n\n static isAsync(opts: { node: unknown }): boolean {\n return HookSupport.isCallback(opts) && readField({ node: opts.node, key: 'async' }) === true;\n }\n\n /**\n * The async functions an argument hands over: the argument itself, a property value of an object literal\n * (`useMutation({ mutationFn: async () => {} })`, an async method too) and an element of an array literal, at any\n * depth of literals. It does not look inside a function: what a sync callback builds is not an argument.\n */\n static asyncFunctionsIn(opts: { node: unknown }): unknown[] {\n const { node } = opts;\n const type = nodeType({ node });\n\n if (HookSupport.isAsync({ node })) {\n return [node];\n }\n\n if (type === 'ObjectExpression') {\n const properties = readField({ node, key: 'properties' });\n\n return Array.isArray(properties)\n ? properties.flatMap((property) =>\n nodeType({ node: property }) === 'Property'\n ? HookSupport.asyncFunctionsIn({ node: readField({ node: property, key: 'value' }) })\n : [],\n )\n : [];\n }\n\n if (type === 'ArrayExpression') {\n const elements = readField({ node, key: 'elements' });\n\n return Array.isArray(elements)\n ? elements.flatMap((element) => HookSupport.asyncFunctionsIn({ node: element }))\n : [];\n }\n\n return [];\n }\n}\n"]}
@@ -1,8 +1,14 @@
1
+ export * from './await-support';
1
2
  export * from './common';
2
3
  export * from './const-class-values.rule';
4
+ export * from './effect-cleanup-consistency.rule';
5
+ export * from './hook-support';
3
6
  export * from './max-params-own.rule';
7
+ export * from './no-async-hook-callback.rule';
8
+ export * from './no-inline-await.rule';
4
9
  export * from './no-leaked-render.rule';
5
10
  export * from './no-new-error.rule';
11
+ export * from './no-return-await.rule';
6
12
  export * from './plugin';
7
13
  export * from './configs';
8
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/linters/house/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/linters/house/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mCAAmC,CAAC;AAClD,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC"}
@@ -14,11 +14,17 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./await-support"), exports);
17
18
  __exportStar(require("./common"), exports);
18
19
  __exportStar(require("./const-class-values.rule"), exports);
20
+ __exportStar(require("./effect-cleanup-consistency.rule"), exports);
21
+ __exportStar(require("./hook-support"), exports);
19
22
  __exportStar(require("./max-params-own.rule"), exports);
23
+ __exportStar(require("./no-async-hook-callback.rule"), exports);
24
+ __exportStar(require("./no-inline-await.rule"), exports);
20
25
  __exportStar(require("./no-leaked-render.rule"), exports);
21
26
  __exportStar(require("./no-new-error.rule"), exports);
27
+ __exportStar(require("./no-return-await.rule"), exports);
22
28
  __exportStar(require("./plugin"), exports);
23
29
  __exportStar(require("./configs"), exports);
24
30
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/linters/house/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,4DAA0C;AAC1C,wDAAsC;AACtC,0DAAwC;AACxC,sDAAoC;AACpC,2CAAyB;AACzB,4CAA0B","sourcesContent":["export * from './common';\nexport * from './const-class-values.rule';\nexport * from './max-params-own.rule';\nexport * from './no-leaked-render.rule';\nexport * from './no-new-error.rule';\nexport * from './plugin';\nexport * from './configs';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/linters/house/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,kDAAgC;AAChC,2CAAyB;AACzB,4DAA0C;AAC1C,oEAAkD;AAClD,iDAA+B;AAC/B,wDAAsC;AACtC,gEAA8C;AAC9C,yDAAuC;AACvC,0DAAwC;AACxC,sDAAoC;AACpC,yDAAuC;AACvC,2CAAyB;AACzB,4CAA0B","sourcesContent":["export * from './await-support';\nexport * from './common';\nexport * from './const-class-values.rule';\nexport * from './effect-cleanup-consistency.rule';\nexport * from './hook-support';\nexport * from './max-params-own.rule';\nexport * from './no-async-hook-callback.rule';\nexport * from './no-inline-await.rule';\nexport * from './no-leaked-render.rule';\nexport * from './no-new-error.rule';\nexport * from './no-return-await.rule';\nexport * from './plugin';\nexport * from './configs';\n"]}
@@ -0,0 +1,18 @@
1
+ import type { Rule } from 'eslint';
2
+ import type { TRuleSeverity } from '../rules/common';
3
+ /**
4
+ * `venizia/no-async-hook-callback`. A hook never gets an async function: `useEffect(async () => {})` makes the
5
+ * callback return a promise where React expects a cleanup function or nothing. Pass a sync callback and handle
6
+ * the promise inside it with `.then`, `.catch` and `.finally`. Every argument of a `use[A-Z]...` call is checked,
7
+ * `React.useX` too: an async function as the argument, as the value of a property of an object literal
8
+ * (`useMutation({ mutationFn: async () => {} })`) and as an element of an array literal. A sync callback that
9
+ * builds an async function inside is not an argument and is left alone.
10
+ */
11
+ export declare class NoAsyncHookCallbackRule {
12
+ static readonly NAME = "venizia/no-async-hook-callback";
13
+ static readonly RULE: Rule.RuleModule;
14
+ static build(opts?: {
15
+ severity?: TRuleSeverity;
16
+ }): TRuleSeverity;
17
+ }
18
+ //# sourceMappingURL=no-async-hook-callback.rule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-async-hook-callback.rule.d.ts","sourceRoot":"","sources":["../../../src/linters/house/no-async-hook-callback.rule.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAKrD;;;;;;;GAOG;AACH,qBAAa,uBAAuB;IAClC,MAAM,CAAC,QAAQ,CAAC,IAAI,oCAAsD;IAE1E,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,CA8BnC;IAEF,MAAM,CAAC,KAAK,CAAC,IAAI,GAAE;QAAE,QAAQ,CAAC,EAAE,aAAa,CAAA;KAAO,GAAG,aAAa;CAGrE"}
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NoAsyncHookCallbackRule = void 0;
4
+ const await_support_1 = require("./await-support");
5
+ const common_1 = require("./common");
6
+ const hook_support_1 = require("./hook-support");
7
+ /**
8
+ * `venizia/no-async-hook-callback`. A hook never gets an async function: `useEffect(async () => {})` makes the
9
+ * callback return a promise where React expects a cleanup function or nothing. Pass a sync callback and handle
10
+ * the promise inside it with `.then`, `.catch` and `.finally`. Every argument of a `use[A-Z]...` call is checked,
11
+ * `React.useX` too: an async function as the argument, as the value of a property of an object literal
12
+ * (`useMutation({ mutationFn: async () => {} })`) and as an element of an array literal. A sync callback that
13
+ * builds an async function inside is not an argument and is left alone.
14
+ */
15
+ class NoAsyncHookCallbackRule {
16
+ static { this.NAME = `${common_1.HOUSE_PLUGIN_NAMESPACE}/no-async-hook-callback`; }
17
+ static { this.RULE = {
18
+ meta: {
19
+ type: 'problem',
20
+ schema: [],
21
+ messages: {
22
+ asyncCallback: 'Do not pass an async function to `{{hook}}`; pass a sync callback and handle the promise inside it with .then/.catch/.finally.',
23
+ },
24
+ },
25
+ create: (context) => ({
26
+ [common_1.AstNodeTypes.CALL_EXPRESSION]: (node) => {
27
+ const hook = hook_support_1.HookSupport.calleeName({ call: node });
28
+ if (hook === undefined || !hook_support_1.HookSupport.isHook({ name: hook })) {
29
+ return;
30
+ }
31
+ const args = (0, common_1.readField)({ node, key: 'arguments' });
32
+ if (!Array.isArray(args)) {
33
+ return;
34
+ }
35
+ for (const argument of args) {
36
+ for (const found of hook_support_1.HookSupport.asyncFunctionsIn({ node: argument })) {
37
+ if (await_support_1.AwaitSupport.isNode(found)) {
38
+ context.report({ node: found, messageId: 'asyncCallback', data: { hook } });
39
+ }
40
+ }
41
+ }
42
+ },
43
+ }),
44
+ }; }
45
+ static build(opts = {}) {
46
+ return opts.severity ?? 'error';
47
+ }
48
+ }
49
+ exports.NoAsyncHookCallbackRule = NoAsyncHookCallbackRule;
50
+ //# sourceMappingURL=no-async-hook-callback.rule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-async-hook-callback.rule.js","sourceRoot":"","sources":["../../../src/linters/house/no-async-hook-callback.rule.ts"],"names":[],"mappings":";;;AAEA,mDAA+C;AAC/C,qCAA2E;AAC3E,iDAA6C;AAE7C;;;;;;;GAOG;AACH,MAAa,uBAAuB;aAClB,SAAI,GAAG,GAAG,+BAAsB,yBAAyB,CAAC;aAE1D,SAAI,GAAoB;QACtC,IAAI,EAAE;YACJ,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,EAAE;YACV,QAAQ,EAAE;gBACR,aAAa,EACX,gIAAgI;aACnI;SACF;QACD,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YACpB,CAAC,qBAAY,CAAC,eAAe,CAAC,EAAE,CAAC,IAAe,EAAE,EAAE;gBAClD,MAAM,IAAI,GAAG,0BAAW,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;gBACpD,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,0BAAW,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;oBAC9D,OAAO;gBACT,CAAC;gBAED,MAAM,IAAI,GAAG,IAAA,kBAAS,EAAC,EAAE,IAAI,EAAE,GAAG,EAAE,WAAW,EAAE,CAAC,CAAC;gBACnD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;oBACzB,OAAO;gBACT,CAAC;gBAED,KAAK,MAAM,QAAQ,IAAI,IAAI,EAAE,CAAC;oBAC5B,KAAK,MAAM,KAAK,IAAI,0BAAW,CAAC,gBAAgB,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;wBACrE,IAAI,4BAAY,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;4BAC/B,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;wBAC9E,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;SACF,CAAC;KACH,CAAC;IAEF,MAAM,CAAC,KAAK,CAAC,OAAqC,EAAE;QAClD,OAAO,IAAI,CAAC,QAAQ,IAAI,OAAO,CAAC;IAClC,CAAC;;AArCH,0DAsCC","sourcesContent":["import type { Rule } from 'eslint';\nimport type { TRuleSeverity } from '../rules/common';\nimport { AwaitSupport } from './await-support';\nimport { AstNodeTypes, HOUSE_PLUGIN_NAMESPACE, readField } from './common';\nimport { HookSupport } from './hook-support';\n\n/**\n * `venizia/no-async-hook-callback`. A hook never gets an async function: `useEffect(async () => {})` makes the\n * callback return a promise where React expects a cleanup function or nothing. Pass a sync callback and handle\n * the promise inside it with `.then`, `.catch` and `.finally`. Every argument of a `use[A-Z]...` call is checked,\n * `React.useX` too: an async function as the argument, as the value of a property of an object literal\n * (`useMutation({ mutationFn: async () => {} })`) and as an element of an array literal. A sync callback that\n * builds an async function inside is not an argument and is left alone.\n */\nexport class NoAsyncHookCallbackRule {\n static readonly NAME = `${HOUSE_PLUGIN_NAMESPACE}/no-async-hook-callback`;\n\n static readonly RULE: Rule.RuleModule = {\n meta: {\n type: 'problem',\n schema: [],\n messages: {\n asyncCallback:\n 'Do not pass an async function to `{{hook}}`; pass a sync callback and handle the promise inside it with .then/.catch/.finally.',\n },\n },\n create: (context) => ({\n [AstNodeTypes.CALL_EXPRESSION]: (node: Rule.Node) => {\n const hook = HookSupport.calleeName({ call: node });\n if (hook === undefined || !HookSupport.isHook({ name: hook })) {\n return;\n }\n\n const args = readField({ node, key: 'arguments' });\n if (!Array.isArray(args)) {\n return;\n }\n\n for (const argument of args) {\n for (const found of HookSupport.asyncFunctionsIn({ node: argument })) {\n if (AwaitSupport.isNode(found)) {\n context.report({ node: found, messageId: 'asyncCallback', data: { hook } });\n }\n }\n }\n },\n }),\n };\n\n static build(opts: { severity?: TRuleSeverity } = {}): TRuleSeverity {\n return opts.severity ?? 'error';\n }\n}\n"]}
@@ -0,0 +1,20 @@
1
+ import type { Rule } from 'eslint';
2
+ import type { TRuleSeverity } from '../rules/common';
3
+ /**
4
+ * `venizia/no-inline-await`. `await` stands alone: as a whole statement (`await f();`) or as the whole initializer
5
+ * of a declaration (`const result = await f();`). Inside any other expression - a call argument, a spread, an
6
+ * array or object, a ternary, a logical operator, a template literal, a member access, an assignment - it is
7
+ * reported, and `return g(await f())` is the same report. `return await f()` is `no-return-await`'s.
8
+ *
9
+ * The fix moves the `await` into a `const` in front of the statement. It is made only when nothing that does more
10
+ * than read a value runs before the `await` inside the statement, and when the `await` is not in a branch, a loop
11
+ * test, an unbraced `if` branch or an arrow expression body; those cases are reported and left alone.
12
+ */
13
+ export declare class NoInlineAwaitRule {
14
+ static readonly NAME = "venizia/no-inline-await";
15
+ static readonly RULE: Rule.RuleModule;
16
+ static build(opts?: {
17
+ severity?: TRuleSeverity;
18
+ }): TRuleSeverity;
19
+ }
20
+ //# sourceMappingURL=no-inline-await.rule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-inline-await.rule.d.ts","sourceRoot":"","sources":["../../../src/linters/house/no-inline-await.rule.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAIrD;;;;;;;;;GASG;AACH,qBAAa,iBAAiB;IAC5B,MAAM,CAAC,QAAQ,CAAC,IAAI,6BAA+C;IAEnE,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,CAgEnC;IAEF,MAAM,CAAC,KAAK,CAAC,IAAI,GAAE;QAAE,QAAQ,CAAC,EAAE,aAAa,CAAA;KAAO,GAAG,aAAa;CAGrE"}
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NoInlineAwaitRule = void 0;
4
+ const await_support_1 = require("./await-support");
5
+ const common_1 = require("./common");
6
+ /**
7
+ * `venizia/no-inline-await`. `await` stands alone: as a whole statement (`await f();`) or as the whole initializer
8
+ * of a declaration (`const result = await f();`). Inside any other expression - a call argument, a spread, an
9
+ * array or object, a ternary, a logical operator, a template literal, a member access, an assignment - it is
10
+ * reported, and `return g(await f())` is the same report. `return await f()` is `no-return-await`'s.
11
+ *
12
+ * The fix moves the `await` into a `const` in front of the statement. It is made only when nothing that does more
13
+ * than read a value runs before the `await` inside the statement, and when the `await` is not in a branch, a loop
14
+ * test, an unbraced `if` branch or an arrow expression body; those cases are reported and left alone.
15
+ */
16
+ class NoInlineAwaitRule {
17
+ static { this.NAME = `${common_1.HOUSE_PLUGIN_NAMESPACE}/no-inline-await`; }
18
+ static { this.RULE = {
19
+ meta: {
20
+ type: 'suggestion',
21
+ fixable: 'code',
22
+ schema: [],
23
+ messages: {
24
+ inline: '`await` stands alone as a statement or as the whole initializer of a declaration (`const result = await f();`), not inside an expression.',
25
+ },
26
+ },
27
+ create: (context) => {
28
+ const { sourceCode } = context;
29
+ const taken = new Map();
30
+ return {
31
+ [common_1.AstNodeTypes.AWAIT_EXPRESSION]: (node) => {
32
+ const parent = await_support_1.AwaitSupport.parentOf({ node });
33
+ const parentType = (0, common_1.nodeType)({ node: parent });
34
+ const isWhole = (parentType === 'ExpressionStatement' &&
35
+ (0, common_1.readField)({ node: parent, key: 'expression' }) === node) ||
36
+ (parentType === 'VariableDeclarator' &&
37
+ (0, common_1.readField)({ node: parent, key: 'init' }) === node);
38
+ const isReturned = (parentType === 'ReturnStatement' || parentType === 'ArrowFunctionExpression') &&
39
+ ((0, common_1.readField)({ node: parent, key: 'argument' }) === node ||
40
+ (0, common_1.readField)({ node: parent, key: 'body' }) === node);
41
+ if (isWhole || isReturned) {
42
+ return;
43
+ }
44
+ const statement = await_support_1.AwaitSupport.hoistTarget({ awaitNode: node });
45
+ const canFix = statement !== undefined &&
46
+ !await_support_1.AwaitSupport.hasEffectBefore({ statement, awaitNode: node, sourceCode });
47
+ context.report({
48
+ node,
49
+ messageId: 'inline',
50
+ fix: canFix
51
+ ? (fixer) => {
52
+ const name = await_support_1.AwaitSupport.freshName({
53
+ node,
54
+ base: 'result',
55
+ sourceCode,
56
+ taken,
57
+ });
58
+ const [start, end] = await_support_1.AwaitSupport.replaceRangeOf({ awaitNode: node, sourceCode });
59
+ const indent = await_support_1.AwaitSupport.indentOf({ node: statement, sourceCode });
60
+ return [
61
+ fixer.insertTextBefore(statement, `const ${name} = ${sourceCode.getText(node)};\n${indent}`),
62
+ fixer.replaceTextRange([start, end], name),
63
+ ];
64
+ }
65
+ : null,
66
+ });
67
+ },
68
+ };
69
+ },
70
+ }; }
71
+ static build(opts = {}) {
72
+ return opts.severity ?? 'error';
73
+ }
74
+ }
75
+ exports.NoInlineAwaitRule = NoInlineAwaitRule;
76
+ //# sourceMappingURL=no-inline-await.rule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-inline-await.rule.js","sourceRoot":"","sources":["../../../src/linters/house/no-inline-await.rule.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Rule } from 'eslint';\nimport type { TRuleSeverity } from '../rules/common';\nimport { AwaitSupport } from './await-support';\nimport { AstNodeTypes, HOUSE_PLUGIN_NAMESPACE, nodeType, readField } from './common';\n\n/**\n * `venizia/no-inline-await`. `await` stands alone: as a whole statement (`await f();`) or as the whole initializer\n * of a declaration (`const result = await f();`). Inside any other expression - a call argument, a spread, an\n * array or object, a ternary, a logical operator, a template literal, a member access, an assignment - it is\n * reported, and `return g(await f())` is the same report. `return await f()` is `no-return-await`'s.\n *\n * The fix moves the `await` into a `const` in front of the statement. It is made only when nothing that does more\n * than read a value runs before the `await` inside the statement, and when the `await` is not in a branch, a loop\n * test, an unbraced `if` branch or an arrow expression body; those cases are reported and left alone.\n */\nexport class NoInlineAwaitRule {\n static readonly NAME = `${HOUSE_PLUGIN_NAMESPACE}/no-inline-await`;\n\n static readonly RULE: Rule.RuleModule = {\n meta: {\n type: 'suggestion',\n fixable: 'code',\n schema: [],\n messages: {\n inline:\n '`await` stands alone as a statement or as the whole initializer of a declaration (`const result = await f();`), not inside an expression.',\n },\n },\n create: (context) => {\n const { sourceCode } = context;\n const taken = new Map<unknown, Set<string>>();\n\n return {\n [AstNodeTypes.AWAIT_EXPRESSION]: (node: Rule.Node) => {\n const parent = AwaitSupport.parentOf({ node });\n const parentType = nodeType({ node: parent });\n const isWhole =\n (parentType === 'ExpressionStatement' &&\n readField({ node: parent, key: 'expression' }) === node) ||\n (parentType === 'VariableDeclarator' &&\n readField({ node: parent, key: 'init' }) === node);\n const isReturned =\n (parentType === 'ReturnStatement' || parentType === 'ArrowFunctionExpression') &&\n (readField({ node: parent, key: 'argument' }) === node ||\n readField({ node: parent, key: 'body' }) === node);\n\n if (isWhole || isReturned) {\n return;\n }\n\n const statement = AwaitSupport.hoistTarget({ awaitNode: node });\n const canFix =\n statement !== undefined &&\n !AwaitSupport.hasEffectBefore({ statement, awaitNode: node, sourceCode });\n\n context.report({\n node,\n messageId: 'inline',\n fix: canFix\n ? (fixer) => {\n const name = AwaitSupport.freshName({\n node,\n base: 'result',\n sourceCode,\n taken,\n });\n const [start, end] = AwaitSupport.replaceRangeOf({ awaitNode: node, sourceCode });\n const indent = AwaitSupport.indentOf({ node: statement, sourceCode });\n\n return [\n fixer.insertTextBefore(\n statement,\n `const ${name} = ${sourceCode.getText(node)};\\n${indent}`,\n ),\n fixer.replaceTextRange([start, end], name),\n ];\n }\n : null,\n });\n },\n };\n },\n };\n\n static build(opts: { severity?: TRuleSeverity } = {}): TRuleSeverity {\n return opts.severity ?? 'error';\n }\n}\n"]}
@@ -0,0 +1,15 @@
1
+ import type { Rule } from 'eslint';
2
+ import type { TRuleSeverity } from '../rules/common';
3
+ /**
4
+ * `venizia/no-return-await`. A function returns the promise (`return f()`), never `return await f()`. Where the
5
+ * `await` matters (inside `try`, `catch` or `finally`) it is written on its own line:
6
+ * `const result = await f(); return result;`. An arrow whose body is an `await` is the same: `() => f()`.
7
+ */
8
+ export declare class NoReturnAwaitRule {
9
+ static readonly NAME = "venizia/no-return-await";
10
+ static readonly RULE: Rule.RuleModule;
11
+ static build(opts?: {
12
+ severity?: TRuleSeverity;
13
+ }): TRuleSeverity;
14
+ }
15
+ //# sourceMappingURL=no-return-await.rule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-return-await.rule.d.ts","sourceRoot":"","sources":["../../../src/linters/house/no-return-await.rule.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAyBrD;;;;GAIG;AACH,qBAAa,iBAAiB;IAC5B,MAAM,CAAC,QAAQ,CAAC,IAAI,6BAA+C;IAEnE,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,CAwEnC;IAEF,MAAM,CAAC,KAAK,CAAC,IAAI,GAAE;QAAE,QAAQ,CAAC,EAAE,aAAa,CAAA;KAAO,GAAG,aAAa;CAGrE"}