@venizia/dev-configs 0.4.7-4 → 0.4.7

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.
@@ -3,103 +3,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.reactNamingConventionConfigs = exports.ReactEslintConfigs = void 0;
6
+ exports.reactNamingConventionConfigs = void 0;
7
7
  const typescript_eslint_1 = __importDefault(require("typescript-eslint"));
8
8
  const eslint_1 = require("./eslint");
9
9
  const jsx_files_1 = require("./jsx-files");
10
10
  const rules_1 = require("./rules");
11
- /**
12
- * The base preset plus what a React frontend needs on top: the React, hooks and accessibility rules.
13
- * Frontend code is held to the backend's standard - no floating promise, no `void`, no use before
14
- * declaration - so nothing in the base is relaxed. The plugins come in as options, so a backend that
15
- * never calls this installs none of them.
16
- *
17
- * ```js
18
- * import react from 'eslint-plugin-react';
19
- * import reactHooks from 'eslint-plugin-react-hooks';
20
- * import jsxA11y from 'eslint-plugin-jsx-a11y';
21
- *
22
- * export default ReactEslintConfigs.create({ plugins: { react, reactHooks, jsxA11y } });
23
- * ```
24
- *
25
- * `reactVersion` defaults to `detect`. Under ESLint 10, eslint-plugin-react 7 cannot detect it and
26
- * aborts, so pass the version there (`reactVersion: '19.2'`).
27
- */
28
- class ReactEslintConfigs {
29
- static create(opts) {
30
- const { react, reactHooks, jsxA11y } = opts.plugins;
31
- const { reactVersion = 'detect' } = opts;
32
- return [
33
- ...eslint_1.eslintConfigs,
34
- {
35
- files: jsx_files_1.JSX_FILES,
36
- plugins: { react, 'jsx-a11y': jsxA11y },
37
- settings: { react: { version: reactVersion } },
38
- rules: {
39
- // The base naming rule, plus PascalCase functions: a component declared as `function App()`.
40
- [rules_1.NamingConventionRule.NAME]: rules_1.NamingConventionRule.build({
41
- extraSelectors: [{ selector: 'function', format: ['camelCase', 'PascalCase'] }],
42
- }),
43
- // Correctness rules from react's recommended set. Left out: what the automatic JSX runtime or
44
- // tsc already covers (react-in-jsx-scope, jsx-uses-react, prop-types, jsx-no-undef,
45
- // jsx-no-duplicate-props, no-unknown-property).
46
- 'react/display-name': 'error',
47
- 'react/jsx-boolean-value': 'error',
48
- 'react/jsx-key': 'error',
49
- 'react/jsx-no-comment-textnodes': 'error',
50
- 'react/jsx-no-target-blank': 'error',
51
- 'react/no-children-prop': 'error',
52
- 'react/no-danger-with-children': 'error',
53
- 'react/no-deprecated': 'error',
54
- 'react/no-direct-mutation-state': 'error',
55
- 'react/no-find-dom-node': 'error',
56
- 'react/no-is-mounted': 'error',
57
- 'react/no-render-return-value': 'error',
58
- 'react/no-string-refs': 'error',
59
- 'react/require-render-return': 'error',
60
- 'jsx-a11y/alt-text': 'error',
61
- 'jsx-a11y/anchor-has-content': 'error',
62
- 'jsx-a11y/aria-props': 'error',
63
- 'jsx-a11y/aria-proptypes': 'error',
64
- 'jsx-a11y/aria-role': 'error',
65
- 'jsx-a11y/aria-unsupported-elements': 'error',
66
- 'jsx-a11y/role-has-required-aria-props': 'error',
67
- 'jsx-a11y/role-supports-aria-props': 'error',
68
- 'jsx-a11y/tabindex-no-positive': 'error',
69
- 'jsx-a11y/anchor-is-valid': 'error',
70
- 'jsx-a11y/click-events-have-key-events': 'error',
71
- 'jsx-a11y/heading-has-content': 'error',
72
- 'jsx-a11y/label-has-associated-control': 'error',
73
- 'jsx-a11y/no-noninteractive-element-interactions': 'error',
74
- 'jsx-a11y/no-static-element-interactions': 'error',
75
- },
76
- },
77
- {
78
- // Custom hooks live in .ts files too.
79
- files: ['**/*.{js,jsx,ts,tsx}'],
80
- plugins: { 'react-hooks': reactHooks },
81
- rules: {
82
- 'react-hooks/rules-of-hooks': 'error',
83
- 'react-hooks/exhaustive-deps': 'error',
84
- },
85
- },
86
- {
87
- rules: {
88
- '@typescript-eslint/consistent-type-imports': [
89
- 'error',
90
- {
91
- prefer: 'type-imports',
92
- fixStyle: 'inline-type-imports',
93
- disallowTypeAnnotations: false,
94
- },
95
- ],
96
- [rules_1.NoRestrictedPropertiesRule.NAME]: rules_1.NoRestrictedPropertiesRule.build(),
97
- },
98
- },
99
- ];
100
- }
101
- }
102
- exports.ReactEslintConfigs = ReactEslintConfigs;
103
11
  /** A component is declared as `function App()`, so functions may be PascalCase too. */
104
12
  const COMPONENT_FUNCTION_SELECTOR = { selector: 'function', format: ['camelCase', 'PascalCase'] };
105
13
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"react-eslint.js","sourceRoot":"","sources":["../../src/linters/react-eslint.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ESLint, Linter } from 'eslint';\nimport tseslint from 'typescript-eslint';\nimport { eslintConfigs, namingConventionConfigs } from './eslint';\nimport { JSX_FILES } from './jsx-files';\nimport { NamingConventionRule, NoRestrictedPropertiesRule } from './rules';\n\n/** Without `configs`: each plugin types its presets its own way, and only the rules are used here. */\ntype TRulesPlugin = Omit<ESLint.Plugin, 'configs'>;\n\nexport interface IReactEslintPlugins {\n react: TRulesPlugin;\n reactHooks: TRulesPlugin;\n jsxA11y: TRulesPlugin;\n}\n\n/**\n * The base preset plus what a React frontend needs on top: the React, hooks and accessibility rules.\n * Frontend code is held to the backend's standard - no floating promise, no `void`, no use before\n * declaration - so nothing in the base is relaxed. The plugins come in as options, so a backend that\n * never calls this installs none of them.\n *\n * ```js\n * import react from 'eslint-plugin-react';\n * import reactHooks from 'eslint-plugin-react-hooks';\n * import jsxA11y from 'eslint-plugin-jsx-a11y';\n *\n * export default ReactEslintConfigs.create({ plugins: { react, reactHooks, jsxA11y } });\n * ```\n *\n * `reactVersion` defaults to `detect`. Under ESLint 10, eslint-plugin-react 7 cannot detect it and\n * aborts, so pass the version there (`reactVersion: '19.2'`).\n */\nexport class ReactEslintConfigs {\n static create(opts: { plugins: IReactEslintPlugins; reactVersion?: string }): Linter.Config[] {\n const { react, reactHooks, jsxA11y } = opts.plugins;\n const { reactVersion = 'detect' } = opts;\n\n return [\n ...eslintConfigs,\n {\n files: JSX_FILES,\n plugins: { react, 'jsx-a11y': jsxA11y },\n settings: { react: { version: reactVersion } },\n rules: {\n // The base naming rule, plus PascalCase functions: a component declared as `function App()`.\n [NamingConventionRule.NAME]: NamingConventionRule.build({\n extraSelectors: [{ selector: 'function', format: ['camelCase', 'PascalCase'] }],\n }),\n\n // Correctness rules from react's recommended set. Left out: what the automatic JSX runtime or\n // tsc already covers (react-in-jsx-scope, jsx-uses-react, prop-types, jsx-no-undef,\n // jsx-no-duplicate-props, no-unknown-property).\n 'react/display-name': 'error',\n 'react/jsx-boolean-value': 'error',\n 'react/jsx-key': 'error',\n 'react/jsx-no-comment-textnodes': 'error',\n 'react/jsx-no-target-blank': 'error',\n 'react/no-children-prop': 'error',\n 'react/no-danger-with-children': 'error',\n 'react/no-deprecated': 'error',\n 'react/no-direct-mutation-state': 'error',\n 'react/no-find-dom-node': 'error',\n 'react/no-is-mounted': 'error',\n 'react/no-render-return-value': 'error',\n 'react/no-string-refs': 'error',\n 'react/require-render-return': 'error',\n\n 'jsx-a11y/alt-text': 'error',\n 'jsx-a11y/anchor-has-content': 'error',\n 'jsx-a11y/aria-props': 'error',\n 'jsx-a11y/aria-proptypes': 'error',\n 'jsx-a11y/aria-role': 'error',\n 'jsx-a11y/aria-unsupported-elements': 'error',\n 'jsx-a11y/role-has-required-aria-props': 'error',\n 'jsx-a11y/role-supports-aria-props': 'error',\n 'jsx-a11y/tabindex-no-positive': 'error',\n 'jsx-a11y/anchor-is-valid': 'error',\n 'jsx-a11y/click-events-have-key-events': 'error',\n 'jsx-a11y/heading-has-content': 'error',\n 'jsx-a11y/label-has-associated-control': 'error',\n 'jsx-a11y/no-noninteractive-element-interactions': 'error',\n 'jsx-a11y/no-static-element-interactions': 'error',\n },\n },\n {\n // Custom hooks live in .ts files too.\n files: ['**/*.{js,jsx,ts,tsx}'],\n plugins: { 'react-hooks': reactHooks },\n rules: {\n 'react-hooks/rules-of-hooks': 'error',\n 'react-hooks/exhaustive-deps': 'error',\n },\n },\n {\n rules: {\n '@typescript-eslint/consistent-type-imports': [\n 'error',\n {\n prefer: 'type-imports',\n fixStyle: 'inline-type-imports',\n disallowTypeAnnotations: false,\n },\n ],\n [NoRestrictedPropertiesRule.NAME]: NoRestrictedPropertiesRule.build() as Linter.RuleEntry,\n },\n },\n ];\n }\n}\n\n/** A component is declared as `function App()`, so functions may be PascalCase too. */\nconst COMPONENT_FUNCTION_SELECTOR = { selector: 'function', format: ['camelCase', 'PascalCase'] };\n\n/**\n * `namingConventionConfigs` for a React project: the base rule plus PascalCase functions in .jsx and\n * .tsx files, where a component is declared as `function App()`.\n */\nexport const reactNamingConventionConfigs: Linter.Config[] = [\n ...namingConventionConfigs,\n {\n files: JSX_FILES,\n languageOptions: { parser: tseslint.parser as Linter.Parser },\n plugins: { '@typescript-eslint': tseslint.plugin as never },\n rules: {\n [NamingConventionRule.NAME]: NamingConventionRule.build({\n extraSelectors: [COMPONENT_FUNCTION_SELECTOR],\n }),\n },\n },\n];\n"]}
1
+ {"version":3,"file":"react-eslint.js","sourceRoot":"","sources":["../../src/linters/react-eslint.ts"],"names":[],"mappings":";;;;;;AACA,0EAAyC;AACzC,qCAAmD;AACnD,2CAAwC;AACxC,mCAA+C;AAE/C,uFAAuF;AACvF,MAAM,2BAA2B,GAAG,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,WAAW,EAAE,YAAY,CAAC,EAAE,CAAC;AAElG;;;GAGG;AACU,QAAA,4BAA4B,GAAoB;IAC3D,GAAG,gCAAuB;IAC1B;QACE,KAAK,EAAE,qBAAS;QAChB,eAAe,EAAE,EAAE,MAAM,EAAE,2BAAQ,CAAC,MAAuB,EAAE;QAC7D,OAAO,EAAE,EAAE,oBAAoB,EAAE,2BAAQ,CAAC,MAAe,EAAE;QAC3D,KAAK,EAAE;YACL,CAAC,4BAAoB,CAAC,IAAI,CAAC,EAAE,4BAAoB,CAAC,KAAK,CAAC;gBACtD,cAAc,EAAE,CAAC,2BAA2B,CAAC;aAC9C,CAAC;SACH;KACF;CACF,CAAC","sourcesContent":["import type { Linter } from 'eslint';\nimport tseslint from 'typescript-eslint';\nimport { namingConventionConfigs } from './eslint';\nimport { JSX_FILES } from './jsx-files';\nimport { NamingConventionRule } from './rules';\n\n/** A component is declared as `function App()`, so functions may be PascalCase too. */\nconst COMPONENT_FUNCTION_SELECTOR = { selector: 'function', format: ['camelCase', 'PascalCase'] };\n\n/**\n * `namingConventionConfigs` for a React project: the base rule plus PascalCase functions in .jsx and\n * .tsx files, where a component is declared as `function App()`.\n */\nexport const reactNamingConventionConfigs: Linter.Config[] = [\n ...namingConventionConfigs,\n {\n files: JSX_FILES,\n languageOptions: { parser: tseslint.parser as Linter.Parser },\n plugins: { '@typescript-eslint': tseslint.plugin as never },\n rules: {\n [NamingConventionRule.NAME]: NamingConventionRule.build({\n extraSelectors: [COMPONENT_FUNCTION_SELECTOR],\n }),\n },\n },\n];\n"]}
@@ -1,10 +1,9 @@
1
1
  import type { OxlintConfig } from 'oxlint';
2
2
  import type { IOxlintPresetOptions } from './oxlint';
3
3
  /**
4
- * The oxlint counterpart of `ReactEslintConfigs`: `BrowserOxlintConfigs` (the base rules plus the
5
- * secure-context ban) plus the React, hooks and accessibility rules and `consistent-type-imports`.
6
- * oxlint runs these plugins natively, so nothing is passed in. `react/no-deprecated` has no oxlint
7
- * port and is left out.
4
+ * The React preset: `BrowserOxlintConfigs` (the base rules plus the secure-context ban) plus the React,
5
+ * hooks and accessibility rules and `consistent-type-imports`. oxlint runs these plugins natively,
6
+ * so nothing is passed in. `react/no-deprecated` has no oxlint port and is left out.
8
7
  *
9
8
  * Nothing in the base is relaxed - frontend code meets the backend's standard - and
10
9
  * `exhaustive-deps` is an error. Takes the same options as `OxlintConfigs`.
@@ -1 +1 @@
1
- {"version":3,"file":"react-oxlint.d.ts","sourceRoot":"","sources":["../../src/linters/react-oxlint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAI3C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAKrD;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,kBAAkB;IAC7B,MAAM,CAAC,MAAM,CAAC,IAAI,GAAE,oBAAyB,GAAG,YAAY;CAgE7D"}
1
+ {"version":3,"file":"react-oxlint.d.ts","sourceRoot":"","sources":["../../src/linters/react-oxlint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAI3C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAKrD;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,kBAAkB;IAC7B,MAAM,CAAC,MAAM,CAAC,IAAI,GAAE,oBAAyB,GAAG,YAAY;CAgE7D"}
@@ -7,10 +7,9 @@ const jsx_files_1 = require("./jsx-files");
7
7
  // This file imports no ESLint package, so a config that only needs the oxlint preset does not load them.
8
8
  const SOURCE_FILES = ['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'];
9
9
  /**
10
- * The oxlint counterpart of `ReactEslintConfigs`: `BrowserOxlintConfigs` (the base rules plus the
11
- * secure-context ban) plus the React, hooks and accessibility rules and `consistent-type-imports`.
12
- * oxlint runs these plugins natively, so nothing is passed in. `react/no-deprecated` has no oxlint
13
- * port and is left out.
10
+ * The React preset: `BrowserOxlintConfigs` (the base rules plus the secure-context ban) plus the React,
11
+ * hooks and accessibility rules and `consistent-type-imports`. oxlint runs these plugins natively,
12
+ * so nothing is passed in. `react/no-deprecated` has no oxlint port and is left out.
14
13
  *
15
14
  * Nothing in the base is relaxed - frontend code meets the backend's standard - and
16
15
  * `exhaustive-deps` is an error. Takes the same options as `OxlintConfigs`.
@@ -34,7 +33,7 @@ class ReactOxlintConfigs {
34
33
  {
35
34
  files: jsx_files_1.JSX_FILES,
36
35
  rules: {
37
- // Correctness rules from react's recommended set, as in ReactEslintConfigs.
36
+ // Correctness rules from react's recommended set.
38
37
  'react/display-name': 'error',
39
38
  'react/jsx-boolean-value': 'error',
40
39
  'react/jsx-key': 'error',
@@ -1 +1 @@
1
- {"version":3,"file":"react-oxlint.js","sourceRoot":"","sources":["../../src/linters/react-oxlint.ts"],"names":[],"mappings":";;;AACA,uCAAiD;AACjD,qCAAsC;AACtC,2CAAwC;AAGxC,yGAAyG;AACzG,MAAM,YAAY,GAAG,CAAC,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;AAE5F;;;;;;;;;;;;;;;;GAgBG;AACH,MAAa,kBAAkB;IAC7B,MAAM,CAAC,MAAM,CAAC,OAA6B,EAAE;QAC3C,MAAM,cAAc,GAAG,8BAAoB,CAAC,MAAM,CAAC,EAAE,eAAe,EAAE,IAAI,CAAC,eAAe,EAAE,CAAC,CAAC;QAE9F,MAAM,MAAM,GAAiB;YAC3B,GAAG,cAAc;YACjB,OAAO,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,UAAU,CAAU,CAAC,CAAC;YACxF,SAAS,EAAE;gBACT,GAAG,CAAC,cAAc,CAAC,SAAS,IAAI,EAAE,CAAC;gBACnC;oBACE,KAAK,EAAE,qBAAS;oBAChB,KAAK,EAAE;wBACL,4EAA4E;wBAC5E,oBAAoB,EAAE,OAAO;wBAC7B,yBAAyB,EAAE,OAAO;wBAClC,eAAe,EAAE,OAAO;wBACxB,gCAAgC,EAAE,OAAO;wBACzC,2BAA2B,EAAE,OAAO;wBACpC,wBAAwB,EAAE,OAAO;wBACjC,+BAA+B,EAAE,OAAO;wBACxC,gCAAgC,EAAE,OAAO;wBACzC,wBAAwB,EAAE,OAAO;wBACjC,qBAAqB,EAAE,OAAO;wBAC9B,8BAA8B,EAAE,OAAO;wBACvC,sBAAsB,EAAE,OAAO;wBAC/B,6BAA6B,EAAE,OAAO;wBAEtC,mBAAmB,EAAE,OAAO;wBAC5B,6BAA6B,EAAE,OAAO;wBACtC,qBAAqB,EAAE,OAAO;wBAC9B,yBAAyB,EAAE,OAAO;wBAClC,oBAAoB,EAAE,OAAO;wBAC7B,oCAAoC,EAAE,OAAO;wBAC7C,uCAAuC,EAAE,OAAO;wBAChD,mCAAmC,EAAE,OAAO;wBAC5C,+BAA+B,EAAE,OAAO;wBACxC,0BAA0B,EAAE,OAAO;wBACnC,uCAAuC,EAAE,OAAO;wBAChD,8BAA8B,EAAE,OAAO;wBACvC,uCAAuC,EAAE,OAAO;wBAChD,iDAAiD,EAAE,OAAO;wBAC1D,yCAAyC,EAAE,OAAO;qBACnD;iBACF;gBACD;oBACE,sCAAsC;oBACtC,KAAK,EAAE,YAAY;oBACnB,KAAK,EAAE;wBACL,sBAAsB,EAAE,OAAO;wBAC/B,uBAAuB,EAAE,OAAO;wBAChC,oCAAoC,EAAE;4BACpC,OAAO;4BACP;gCACE,MAAM,EAAE,cAAc;gCACtB,QAAQ,EAAE,qBAAqB;gCAC/B,uBAAuB,EAAE,KAAK;6BAC/B;yBACF;qBACF;iBACF;aACF;SACF,CAAC;QAEF,OAAO,mBAAU,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC,CAAC;IACpE,CAAC;CACF;AAjED,gDAiEC","sourcesContent":["import type { OxlintConfig } from 'oxlint';\nimport { BrowserOxlintConfigs } from './browser';\nimport { LintIgnore } from './ignore';\nimport { JSX_FILES } from './jsx-files';\nimport type { IOxlintPresetOptions } from './oxlint';\n\n// This file imports no ESLint package, so a config that only needs the oxlint preset does not load them.\nconst SOURCE_FILES = ['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'];\n\n/**\n * The oxlint counterpart of `ReactEslintConfigs`: `BrowserOxlintConfigs` (the base rules plus the\n * secure-context ban) plus the React, hooks and accessibility rules and `consistent-type-imports`.\n * oxlint runs these plugins natively, so nothing is passed in. `react/no-deprecated` has no oxlint\n * port and is left out.\n *\n * Nothing in the base is relaxed - frontend code meets the backend's standard - and\n * `exhaustive-deps` is an error. Takes the same options as `OxlintConfigs`.\n *\n * ```ts\n * // .configs/lints/oxlint.config.mts\n * import { defineConfig } from 'oxlint';\n * import { ReactOxlintConfigs } from '@venizia/dev-configs/linters/oxlint';\n *\n * export default defineConfig(ReactOxlintConfigs.create());\n * ```\n */\nexport class ReactOxlintConfigs {\n static create(opts: IOxlintPresetOptions = {}): OxlintConfig {\n const browserConfigs = BrowserOxlintConfigs.create({ lenientFallback: opts.lenientFallback });\n\n const config: OxlintConfig = {\n ...browserConfigs,\n plugins: [...new Set([...(browserConfigs.plugins ?? []), 'react', 'jsx-a11y'] as const)],\n overrides: [\n ...(browserConfigs.overrides ?? []),\n {\n files: JSX_FILES,\n rules: {\n // Correctness rules from react's recommended set, as in ReactEslintConfigs.\n 'react/display-name': 'error',\n 'react/jsx-boolean-value': 'error',\n 'react/jsx-key': 'error',\n 'react/jsx-no-comment-textnodes': 'error',\n 'react/jsx-no-target-blank': 'error',\n 'react/no-children-prop': 'error',\n 'react/no-danger-with-children': 'error',\n 'react/no-direct-mutation-state': 'error',\n 'react/no-find-dom-node': 'error',\n 'react/no-is-mounted': 'error',\n 'react/no-render-return-value': 'error',\n 'react/no-string-refs': 'error',\n 'react/require-render-return': 'error',\n\n 'jsx-a11y/alt-text': 'error',\n 'jsx-a11y/anchor-has-content': 'error',\n 'jsx-a11y/aria-props': 'error',\n 'jsx-a11y/aria-proptypes': 'error',\n 'jsx-a11y/aria-role': 'error',\n 'jsx-a11y/aria-unsupported-elements': 'error',\n 'jsx-a11y/role-has-required-aria-props': 'error',\n 'jsx-a11y/role-supports-aria-props': 'error',\n 'jsx-a11y/tabindex-no-positive': 'error',\n 'jsx-a11y/anchor-is-valid': 'error',\n 'jsx-a11y/click-events-have-key-events': 'error',\n 'jsx-a11y/heading-has-content': 'error',\n 'jsx-a11y/label-has-associated-control': 'error',\n 'jsx-a11y/no-noninteractive-element-interactions': 'error',\n 'jsx-a11y/no-static-element-interactions': 'error',\n },\n },\n {\n // Custom hooks live in .ts files too.\n files: SOURCE_FILES,\n rules: {\n 'react/rules-of-hooks': 'error',\n 'react/exhaustive-deps': 'error',\n 'typescript/consistent-type-imports': [\n 'error',\n {\n prefer: 'type-imports',\n fixStyle: 'inline-type-imports',\n disallowTypeAnnotations: false,\n },\n ],\n },\n },\n ],\n };\n\n return LintIgnore.oxlint({ config, patterns: opts.ignore ?? [] });\n }\n}\n"]}
1
+ {"version":3,"file":"react-oxlint.js","sourceRoot":"","sources":["../../src/linters/react-oxlint.ts"],"names":[],"mappings":";;;AACA,uCAAiD;AACjD,qCAAsC;AACtC,2CAAwC;AAGxC,yGAAyG;AACzG,MAAM,YAAY,GAAG,CAAC,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;AAE5F;;;;;;;;;;;;;;;GAeG;AACH,MAAa,kBAAkB;IAC7B,MAAM,CAAC,MAAM,CAAC,OAA6B,EAAE;QAC3C,MAAM,cAAc,GAAG,8BAAoB,CAAC,MAAM,CAAC,EAAE,eAAe,EAAE,IAAI,CAAC,eAAe,EAAE,CAAC,CAAC;QAE9F,MAAM,MAAM,GAAiB;YAC3B,GAAG,cAAc;YACjB,OAAO,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,UAAU,CAAU,CAAC,CAAC;YACxF,SAAS,EAAE;gBACT,GAAG,CAAC,cAAc,CAAC,SAAS,IAAI,EAAE,CAAC;gBACnC;oBACE,KAAK,EAAE,qBAAS;oBAChB,KAAK,EAAE;wBACL,kDAAkD;wBAClD,oBAAoB,EAAE,OAAO;wBAC7B,yBAAyB,EAAE,OAAO;wBAClC,eAAe,EAAE,OAAO;wBACxB,gCAAgC,EAAE,OAAO;wBACzC,2BAA2B,EAAE,OAAO;wBACpC,wBAAwB,EAAE,OAAO;wBACjC,+BAA+B,EAAE,OAAO;wBACxC,gCAAgC,EAAE,OAAO;wBACzC,wBAAwB,EAAE,OAAO;wBACjC,qBAAqB,EAAE,OAAO;wBAC9B,8BAA8B,EAAE,OAAO;wBACvC,sBAAsB,EAAE,OAAO;wBAC/B,6BAA6B,EAAE,OAAO;wBAEtC,mBAAmB,EAAE,OAAO;wBAC5B,6BAA6B,EAAE,OAAO;wBACtC,qBAAqB,EAAE,OAAO;wBAC9B,yBAAyB,EAAE,OAAO;wBAClC,oBAAoB,EAAE,OAAO;wBAC7B,oCAAoC,EAAE,OAAO;wBAC7C,uCAAuC,EAAE,OAAO;wBAChD,mCAAmC,EAAE,OAAO;wBAC5C,+BAA+B,EAAE,OAAO;wBACxC,0BAA0B,EAAE,OAAO;wBACnC,uCAAuC,EAAE,OAAO;wBAChD,8BAA8B,EAAE,OAAO;wBACvC,uCAAuC,EAAE,OAAO;wBAChD,iDAAiD,EAAE,OAAO;wBAC1D,yCAAyC,EAAE,OAAO;qBACnD;iBACF;gBACD;oBACE,sCAAsC;oBACtC,KAAK,EAAE,YAAY;oBACnB,KAAK,EAAE;wBACL,sBAAsB,EAAE,OAAO;wBAC/B,uBAAuB,EAAE,OAAO;wBAChC,oCAAoC,EAAE;4BACpC,OAAO;4BACP;gCACE,MAAM,EAAE,cAAc;gCACtB,QAAQ,EAAE,qBAAqB;gCAC/B,uBAAuB,EAAE,KAAK;6BAC/B;yBACF;qBACF;iBACF;aACF;SACF,CAAC;QAEF,OAAO,mBAAU,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC,CAAC;IACpE,CAAC;CACF;AAjED,gDAiEC","sourcesContent":["import type { OxlintConfig } from 'oxlint';\nimport { BrowserOxlintConfigs } from './browser';\nimport { LintIgnore } from './ignore';\nimport { JSX_FILES } from './jsx-files';\nimport type { IOxlintPresetOptions } from './oxlint';\n\n// This file imports no ESLint package, so a config that only needs the oxlint preset does not load them.\nconst SOURCE_FILES = ['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'];\n\n/**\n * The React preset: `BrowserOxlintConfigs` (the base rules plus the secure-context ban) plus the React,\n * hooks and accessibility rules and `consistent-type-imports`. oxlint runs these plugins natively,\n * so nothing is passed in. `react/no-deprecated` has no oxlint port and is left out.\n *\n * Nothing in the base is relaxed - frontend code meets the backend's standard - and\n * `exhaustive-deps` is an error. Takes the same options as `OxlintConfigs`.\n *\n * ```ts\n * // .configs/lints/oxlint.config.mts\n * import { defineConfig } from 'oxlint';\n * import { ReactOxlintConfigs } from '@venizia/dev-configs/linters/oxlint';\n *\n * export default defineConfig(ReactOxlintConfigs.create());\n * ```\n */\nexport class ReactOxlintConfigs {\n static create(opts: IOxlintPresetOptions = {}): OxlintConfig {\n const browserConfigs = BrowserOxlintConfigs.create({ lenientFallback: opts.lenientFallback });\n\n const config: OxlintConfig = {\n ...browserConfigs,\n plugins: [...new Set([...(browserConfigs.plugins ?? []), 'react', 'jsx-a11y'] as const)],\n overrides: [\n ...(browserConfigs.overrides ?? []),\n {\n files: JSX_FILES,\n rules: {\n // Correctness rules from react's recommended set.\n 'react/display-name': 'error',\n 'react/jsx-boolean-value': 'error',\n 'react/jsx-key': 'error',\n 'react/jsx-no-comment-textnodes': 'error',\n 'react/jsx-no-target-blank': 'error',\n 'react/no-children-prop': 'error',\n 'react/no-danger-with-children': 'error',\n 'react/no-direct-mutation-state': 'error',\n 'react/no-find-dom-node': 'error',\n 'react/no-is-mounted': 'error',\n 'react/no-render-return-value': 'error',\n 'react/no-string-refs': 'error',\n 'react/require-render-return': 'error',\n\n 'jsx-a11y/alt-text': 'error',\n 'jsx-a11y/anchor-has-content': 'error',\n 'jsx-a11y/aria-props': 'error',\n 'jsx-a11y/aria-proptypes': 'error',\n 'jsx-a11y/aria-role': 'error',\n 'jsx-a11y/aria-unsupported-elements': 'error',\n 'jsx-a11y/role-has-required-aria-props': 'error',\n 'jsx-a11y/role-supports-aria-props': 'error',\n 'jsx-a11y/tabindex-no-positive': 'error',\n 'jsx-a11y/anchor-is-valid': 'error',\n 'jsx-a11y/click-events-have-key-events': 'error',\n 'jsx-a11y/heading-has-content': 'error',\n 'jsx-a11y/label-has-associated-control': 'error',\n 'jsx-a11y/no-noninteractive-element-interactions': 'error',\n 'jsx-a11y/no-static-element-interactions': 'error',\n },\n },\n {\n // Custom hooks live in .ts files too.\n files: SOURCE_FILES,\n rules: {\n 'react/rules-of-hooks': 'error',\n 'react/exhaustive-deps': 'error',\n 'typescript/consistent-type-imports': [\n 'error',\n {\n prefer: 'type-imports',\n fixStyle: 'inline-type-imports',\n disallowTypeAnnotations: false,\n },\n ],\n },\n },\n ],\n };\n\n return LintIgnore.oxlint({ config, patterns: opts.ignore ?? [] });\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@venizia/dev-configs",
3
- "version": "0.4.7-4",
3
+ "version": "0.4.7",
4
4
  "description": "Shareable development configurations for TypeScript projects: oxlint presets with per-rule builders, the ESLint naming-convention rule, the legacy ESLint flat config, and TypeScript compiler options with critical decorator support (experimentalDecorators, emitDecoratorMetadata, useDefineForClassFields). Single source of truth for consistent code style, type checking, and build settings across monorepo packages.",
5
5
  "keywords": [
6
6
  "build-tools",
@@ -5,8 +5,5 @@
5
5
  "target": "ES2025",
6
6
  "module": "esnext",
7
7
  "moduleResolution": "bundler"
8
- },
9
- "ts-node": {
10
- "transpileOnly": true
11
8
  }
12
9
  }
@@ -1,36 +0,0 @@
1
- import type ts from 'typescript';
2
- type TTypeScript = typeof ts;
3
- /** One thing the codemod could not decide and left alone. */
4
- export interface IModelSettingsReport {
5
- line: number;
6
- column: number;
7
- reason: string;
8
- }
9
- export interface IModelSettingsResult {
10
- /** The source after the migration; equal to the input when nothing changed. */
11
- output: string;
12
- changed: boolean;
13
- /** How many `@model` settings objects were rewritten. */
14
- rewritten: number;
15
- reports: IModelSettingsReport[];
16
- }
17
- /**
18
- * Rewrites `@model({ ... })` from the old keys to the grouped ones. `skipMigrate` becomes
19
- * `migration: { use: false }`. In the `settings` object literal:
20
- * `defaultFilter` to `query.filter`, `defaultLimit` and `maxLimit` to `query.limit.default` and
21
- * `query.limit.max`, `scopeFilter` to `access` (`writeGuard` to `write.mode`, `sharedRows` to
22
- * `write.sharedRows`).
23
- *
24
- * It edits by source range, so comments and formatting outside the rewritten properties stay. It
25
- * never guesses: a `settings` value that is not an object literal, a spread, a computed key, or a
26
- * `scopeFilter` that is not an object literal is reported and the model is left untouched.
27
- */
28
- export declare class ModelSettingsCodemod {
29
- static migrate(opts: {
30
- typescript: TTypeScript;
31
- fileName: string;
32
- source: string;
33
- }): IModelSettingsResult;
34
- }
35
- export {};
36
- //# sourceMappingURL=model-settings.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"model-settings.d.ts","sourceRoot":"","sources":["../../../src/cli/codemod/model-settings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAEjC,KAAK,WAAW,GAAG,OAAO,EAAE,CAAC;AAK7B,6DAA6D;AAC7D,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,+EAA+E;IAC/E,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,oBAAoB,EAAE,CAAC;CACjC;AAWD;;;;;;;;;;GAUG;AACH,qBAAa,oBAAoB;IAC/B,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE;QACnB,UAAU,EAAE,WAAW,CAAC;QACxB,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;KAChB,GAAG,oBAAoB;CA+azB"}
@@ -1,346 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ModelSettingsCodemod = void 0;
4
- /** The packages that export the `@model` decorator, so an aliased import of it is recognised. */
5
- const MODEL_MODULES = new Set(['@venizia/ignis', '@venizia/ignis-kernel']);
6
- const QUERY_KEYS = { defaultFilter: 'filter', defaultLimit: 'default', maxLimit: 'max' };
7
- const OLD_KEYS = new Set([...Object.keys(QUERY_KEYS), 'scopeFilter']);
8
- /**
9
- * Rewrites `@model({ ... })` from the old keys to the grouped ones. `skipMigrate` becomes
10
- * `migration: { use: false }`. In the `settings` object literal:
11
- * `defaultFilter` to `query.filter`, `defaultLimit` and `maxLimit` to `query.limit.default` and
12
- * `query.limit.max`, `scopeFilter` to `access` (`writeGuard` to `write.mode`, `sharedRows` to
13
- * `write.sharedRows`).
14
- *
15
- * It edits by source range, so comments and formatting outside the rewritten properties stay. It
16
- * never guesses: a `settings` value that is not an object literal, a spread, a computed key, or a
17
- * `scopeFilter` that is not an object literal is reported and the model is left untouched.
18
- */
19
- class ModelSettingsCodemod {
20
- static migrate(opts) {
21
- const { typescript, fileName, source } = opts;
22
- const sourceFile = typescript.createSourceFile(fileName, source, typescript.ScriptTarget.Latest, true);
23
- const edits = [];
24
- const reports = [];
25
- let rewritten = 0;
26
- const report = (node, reason) => {
27
- const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
28
- reports.push({ line: line + 1, column: character + 1, reason });
29
- };
30
- const nameOf = (property) => {
31
- if (!typescript.isPropertyAssignment(property) &&
32
- !typescript.isShorthandPropertyAssignment(property)) {
33
- return undefined;
34
- }
35
- const { name } = property;
36
- if (typescript.isIdentifier(name) || typescript.isStringLiteral(name)) {
37
- return name.text;
38
- }
39
- return undefined;
40
- };
41
- const text = (node) => source.slice(node.getStart(sourceFile), node.end);
42
- // Names that mean `@model`: the identifier itself, an alias of a named import from IGNIS, and the
43
- // namespace of `import * as ignis` (`@ignis.model(...)`).
44
- const modelNames = new Set(['model']);
45
- const namespaces = new Set();
46
- for (const statement of sourceFile.statements) {
47
- if (!typescript.isImportDeclaration(statement) ||
48
- !typescript.isStringLiteral(statement.moduleSpecifier) ||
49
- !MODEL_MODULES.has(statement.moduleSpecifier.text)) {
50
- continue;
51
- }
52
- const bindings = statement.importClause?.namedBindings;
53
- if (!bindings) {
54
- continue;
55
- }
56
- if (typescript.isNamespaceImport(bindings)) {
57
- namespaces.add(bindings.name.text);
58
- continue;
59
- }
60
- for (const element of bindings.elements) {
61
- if ((element.propertyName ?? element.name).text === 'model') {
62
- modelNames.add(element.name.text);
63
- }
64
- }
65
- }
66
- const isModelCallee = (callee) => {
67
- if (typescript.isIdentifier(callee)) {
68
- return modelNames.has(callee.text);
69
- }
70
- return (typescript.isPropertyAccessExpression(callee) &&
71
- typescript.isIdentifier(callee.expression) &&
72
- namespaces.has(callee.expression.text) &&
73
- callee.name.text === 'model');
74
- };
75
- /**
76
- * The leading trivia of `first` (its own comments and indentation) followed by the comments that
77
- * were above the properties being merged into it, so no comment is dropped.
78
- */
79
- const composeLeading = (first, merged) => {
80
- const trivia = source.slice(first.getFullStart(), first.getStart(sourceFile));
81
- const indent = /[ \t]*$/.exec(trivia)?.[0] ?? '';
82
- const extra = merged
83
- .flatMap((node) => typescript.getLeadingCommentRanges(source, node.getFullStart()) ?? [])
84
- .map((range) => `${source.slice(range.pos, range.end)}\n${indent}`)
85
- .join('');
86
- return trivia + extra;
87
- };
88
- /**
89
- * Removes a property with the comma after it; the last property has none, so the comma before it
90
- * goes instead, leaving no dangling `, }`.
91
- */
92
- const removal = (node) => {
93
- const after = /^\s*,/.exec(source.slice(node.end));
94
- if (after) {
95
- return { start: node.getFullStart(), end: node.end + after[0].length, text: '' };
96
- }
97
- const before = /,\s*$/.exec(source.slice(0, node.getFullStart()));
98
- const start = before ? node.getFullStart() - before[0].length : node.getFullStart();
99
- return { start, end: node.end, text: '' };
100
- };
101
- /** Appends members to an object literal: after its last member, or inside empty braces. */
102
- const append = (object, members) => {
103
- const last = object.properties[object.properties.length - 1];
104
- if (last) {
105
- return { start: last.end, end: last.end, text: `, ${members.join(', ')}` };
106
- }
107
- const at = object.getStart(sourceFile) + 1;
108
- return { start: at, end: at, text: ` ${members.join(', ')} ` };
109
- };
110
- const migrateSettings = (settings) => {
111
- const properties = [...settings.properties];
112
- const old = new Map();
113
- let existingQuery;
114
- for (const property of properties) {
115
- if (typescript.isSpreadAssignment(property)) {
116
- // A spread can carry old keys we cannot see.
117
- report(property, 'settings contains a spread, so its keys are unknown; left untouched');
118
- return;
119
- }
120
- if (typescript.isMethodDeclaration(property)) {
121
- continue;
122
- }
123
- const name = nameOf(property);
124
- if (name === undefined) {
125
- report(property, 'settings contains a computed key; left untouched');
126
- return;
127
- }
128
- if (OLD_KEYS.has(name)) {
129
- if (!typescript.isPropertyAssignment(property)) {
130
- report(property, `settings.${name} is a shorthand property; left untouched`);
131
- return;
132
- }
133
- old.set(name, property);
134
- }
135
- if (name === 'query' && typescript.isPropertyAssignment(property)) {
136
- existingQuery = property;
137
- }
138
- }
139
- if (old.size === 0) {
140
- return;
141
- }
142
- const queryOld = [...old.keys()].filter((key) => key in QUERY_KEYS);
143
- const localEdits = [];
144
- // ---- scopeFilter -> access (rename the key, rewrite writeGuard / sharedRows inside)
145
- const scopeFilter = old.get('scopeFilter');
146
- if (scopeFilter) {
147
- if (!typescript.isObjectLiteralExpression(scopeFilter.initializer)) {
148
- report(scopeFilter, 'settings.scopeFilter is not an object literal; left untouched');
149
- return;
150
- }
151
- const inner = scopeFilter.initializer;
152
- const guard = inner.properties.find((property) => nameOf(property) === 'writeGuard' && typescript.isPropertyAssignment(property));
153
- const shared = inner.properties.find((property) => nameOf(property) === 'sharedRows' && typescript.isPropertyAssignment(property));
154
- const hasSpread = inner.properties.some((property) => typescript.isSpreadAssignment(property));
155
- if (hasSpread) {
156
- report(scopeFilter, 'settings.scopeFilter contains a spread; left untouched');
157
- return;
158
- }
159
- if (inner.properties.some((property) => nameOf(property) === 'write')) {
160
- report(scopeFilter, 'settings.scopeFilter already has a write key; left untouched');
161
- return;
162
- }
163
- localEdits.push({
164
- start: scopeFilter.name.getStart(sourceFile),
165
- end: scopeFilter.name.end,
166
- text: 'access',
167
- });
168
- if (guard || shared) {
169
- const members = [
170
- guard ? `mode: ${text(guard.initializer)}` : undefined,
171
- shared ? `sharedRows: ${text(shared.initializer)}` : undefined,
172
- ].filter((member) => member !== undefined);
173
- const ordered = [guard, shared]
174
- .filter((property) => property !== undefined)
175
- .sort((a, b) => a.getStart(sourceFile) - b.getStart(sourceFile));
176
- const [first, ...merged] = ordered;
177
- localEdits.push({
178
- start: first.getFullStart(),
179
- end: first.end,
180
- text: `${composeLeading(first, merged)}write: { ${members.join(', ')} }`,
181
- });
182
- for (const second of merged) {
183
- localEdits.push(removal(second));
184
- }
185
- }
186
- }
187
- // ---- defaultFilter / defaultLimit / maxLimit -> query
188
- if (queryOld.length > 0) {
189
- const filter = old.get('defaultFilter');
190
- const defaultLimit = old.get('defaultLimit');
191
- const maxLimit = old.get('maxLimit');
192
- if (existingQuery) {
193
- if (!typescript.isObjectLiteralExpression(existingQuery.initializer)) {
194
- report(existingQuery, 'settings.query is not an object literal; left untouched');
195
- return;
196
- }
197
- const queryObject = existingQuery.initializer;
198
- const queryKeys = new Set(queryObject.properties.map((property) => nameOf(property)));
199
- const existingLimit = queryObject.properties.find((property) => nameOf(property) === 'limit');
200
- if (filter && queryKeys.has('filter')) {
201
- report(filter, 'settings.defaultFilter and settings.query.filter are both set; left untouched');
202
- return;
203
- }
204
- if ((defaultLimit || maxLimit) && existingLimit) {
205
- if (!typescript.isObjectLiteralExpression(existingLimit.initializer)) {
206
- report(existingLimit, 'settings.query.limit is not an object literal; left untouched');
207
- return;
208
- }
209
- const limitKeys = new Set(existingLimit.initializer.properties.map((property) => nameOf(property)));
210
- if ((defaultLimit && limitKeys.has('default')) || (maxLimit && limitKeys.has('max'))) {
211
- report(existingLimit, 'an old limit key and its new key are both set; left untouched');
212
- return;
213
- }
214
- }
215
- const additions = [];
216
- if (filter) {
217
- additions.push(`filter: ${text(filter.initializer)}`);
218
- }
219
- const limitMembers = [
220
- defaultLimit ? `default: ${text(defaultLimit.initializer)}` : undefined,
221
- maxLimit ? `max: ${text(maxLimit.initializer)}` : undefined,
222
- ].filter((member) => member !== undefined);
223
- if (limitMembers.length > 0 && existingLimit) {
224
- localEdits.push(append(existingLimit.initializer, limitMembers));
225
- }
226
- else if (limitMembers.length > 0) {
227
- additions.push(`limit: { ${limitMembers.join(', ')} }`);
228
- }
229
- if (additions.length > 0) {
230
- localEdits.push(append(queryObject, additions));
231
- }
232
- for (const key of queryOld) {
233
- const property = old.get(key);
234
- localEdits.push(removal(property));
235
- }
236
- }
237
- else {
238
- const members = [];
239
- if (filter) {
240
- members.push(`filter: ${text(filter.initializer)}`);
241
- }
242
- const limitMembers = [
243
- defaultLimit ? `default: ${text(defaultLimit.initializer)}` : undefined,
244
- maxLimit ? `max: ${text(maxLimit.initializer)}` : undefined,
245
- ].filter((member) => member !== undefined);
246
- if (limitMembers.length > 0) {
247
- members.push(`limit: { ${limitMembers.join(', ')} }`);
248
- }
249
- const ordered = queryOld
250
- .map((key) => old.get(key))
251
- .sort((a, b) => a.getStart(sourceFile) - b.getStart(sourceFile));
252
- const [first, ...others] = ordered;
253
- localEdits.push({
254
- start: first.getFullStart(),
255
- end: first.end,
256
- text: `${composeLeading(first, others)}query: { ${members.join(', ')} }`,
257
- });
258
- for (const other of others) {
259
- localEdits.push(removal(other));
260
- }
261
- }
262
- }
263
- edits.push(...localEdits);
264
- rewritten += 1;
265
- };
266
- /**
267
- * `skipMigrate: true` becomes `migration: { use: false }`; `skipMigrate: false` is the default and
268
- * is removed. Anything that is not a boolean literal, or a model that already has `migration`, is
269
- * reported and left alone.
270
- */
271
- const migrateSkipMigrate = (argument) => {
272
- const property = argument.properties.find((candidate) => nameOf(candidate) === 'skipMigrate');
273
- if (!property) {
274
- return;
275
- }
276
- if (!typescript.isPropertyAssignment(property)) {
277
- report(property, 'skipMigrate is a shorthand property; left untouched');
278
- return;
279
- }
280
- if (argument.properties.some((candidate) => nameOf(candidate) === 'migration')) {
281
- report(property, 'skipMigrate and migration are both set; left untouched');
282
- return;
283
- }
284
- const { initializer } = property;
285
- if (initializer.kind === typescript.SyntaxKind.TrueKeyword) {
286
- edits.push({
287
- start: property.getStart(sourceFile),
288
- end: property.end,
289
- text: 'migration: { use: false }',
290
- });
291
- }
292
- else if (initializer.kind === typescript.SyntaxKind.FalseKeyword) {
293
- edits.push(removal(property));
294
- }
295
- else {
296
- report(property, 'skipMigrate is not a boolean literal; left untouched');
297
- return;
298
- }
299
- rewritten += 1;
300
- };
301
- const visit = (node) => {
302
- if (typescript.isDecorator(node) &&
303
- typescript.isCallExpression(node.expression) &&
304
- isModelCallee(node.expression.expression)) {
305
- const argument = node.expression.arguments[0];
306
- if (argument && typescript.isObjectLiteralExpression(argument)) {
307
- const settingsProperty = argument.properties.find((property) => nameOf(property) === 'settings');
308
- if (settingsProperty) {
309
- if (typescript.isPropertyAssignment(settingsProperty) &&
310
- typescript.isObjectLiteralExpression(settingsProperty.initializer)) {
311
- migrateSettings(settingsProperty.initializer);
312
- }
313
- else {
314
- report(settingsProperty, 'settings is not an object literal (a variable, a call or a shorthand); left untouched');
315
- }
316
- }
317
- migrateSkipMigrate(argument);
318
- }
319
- else if (argument) {
320
- report(argument, '@model argument is not an object literal (a variable or a call); left untouched');
321
- }
322
- }
323
- typescript.forEachChild(node, visit);
324
- };
325
- visit(sourceFile);
326
- // Neighbouring removals can touch the same comma: merge those, then apply from the end so
327
- // earlier offsets stay valid.
328
- const ascending = [...edits].sort((a, b) => a.start - b.start || a.end - b.end);
329
- const merged = [];
330
- for (const edit of ascending) {
331
- const previous = merged[merged.length - 1];
332
- if (previous && edit.start < previous.end && previous.text === '' && edit.text === '') {
333
- previous.end = Math.max(previous.end, edit.end);
334
- continue;
335
- }
336
- merged.push({ ...edit });
337
- }
338
- let output = source;
339
- for (const edit of merged.reverse()) {
340
- output = output.slice(0, edit.start) + edit.text + output.slice(edit.end);
341
- }
342
- return { output, changed: output !== source, rewritten, reports };
343
- }
344
- }
345
- exports.ModelSettingsCodemod = ModelSettingsCodemod;
346
- //# sourceMappingURL=model-settings.js.map