@eagleoutice/eslint-config-flowr 2.0.1 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,115 @@
1
1
  # Linter rules for flowR
2
2
 
3
- This configuration contains just the eslint-rules.
4
- For flowR's capability of linting R code, please look at the [corresponding wiki](https://github.com/flowr-analysis/flowr/wiki/Linter).
3
+ The eslint configuration for flowR and friends. For flowR's capability of linting R code see the
4
+ [linter wiki page](https://github.com/flowr-analysis/flowr/wiki/Linter), the main project lives at
5
+ <https://github.com/flowr-analysis/flowr>.
5
6
 
6
- See the main project at <https://github.com/flowr-analysis/flowr>.
7
+ ```shell
8
+ npm i -D @eagleoutice/eslint-config-flowr
9
+ ```
10
+
11
+ That is the whole install: everything the configuration uses is a dependency of the package, only `eslint` stays a peer
12
+ dependency because it is the one instance that has to be shared. To register the tags below, extend the shipped TSDoc
13
+ configuration from your `tsdoc.json`:
14
+
15
+ ```json
16
+ { "extends": ["@eagleoutice/eslint-config-flowr/tsdoc.json"] }
17
+ ```
18
+
19
+ ## The `flowr` plugin
20
+
21
+ flowR groups its functions in helper objects (`DfEdge`, `Resolve`, `NodeId`, and friends) so that there is one obvious
22
+ entry point per topic. Two rules keep the code on those entry points, both on by default.
23
+
24
+ | rule | driven by | finds |
25
+ | :-- | :-- | :-- |
26
+ | `flowr/use-instead` | the `@useInstead` tag | a reference to what a helper object replaced |
27
+ | `flowr/replacement-pattern` | [`patterns.ts`](patterns.ts) | a shape of code with a helper equivalent |
28
+
29
+ Both fix in place when every name the replacement needs is already in scope, and offer an editor suggestion otherwise,
30
+ since a fixer cannot add the missing import. A type-only binding of the right name does not count.
31
+
32
+ ### `flowr/use-instead`
33
+
34
+ A function that only exists to be wired into a helper object names its replacement:
35
+
36
+ ```ts
37
+ /**
38
+ * Every definition the identifier may refer to.
39
+ * @useInstead {@link Resolve.byName}
40
+ */
41
+ export function resolveByNameAnyType(/* ... */) { /* ... */ }
42
+ ```
43
+
44
+ Every reference outside the declaring file is then reported. The tag works on a function, on a helper object, and on its
45
+ members. Never reported: the wiring inside an object literal (`byName: resolveByNameAnyType`), re-exports, and files
46
+ that declare the helper the tag points at.
47
+
48
+ Documentation is read once per TypeScript symbol, and member accesses are only inspected for PascalCase objects (the
49
+ `helperObjectPattern` option), so the rule stays cheap on large projects.
50
+
51
+ ### `flowr/replacement-pattern`
52
+
53
+ Patterns are [esquery](https://github.com/estools/esquery) selectors, the language `no-restricted-syntax` uses.
54
+ To add one, append to [`patterns.ts`](patterns.ts):
55
+
56
+ ```js
57
+ {
58
+ id: 'edge-is-only-type',
59
+ selector: 'BinaryExpression[operator="==="][left.property.name="types"]',
60
+ capture: { edge: 'left.object', type: 'right' },
61
+ replace: 'DfEdge.isOnlyType({{edge}}, {{type}})',
62
+ declaredIn: { 'left.property': 'dataflow/graph/edge' },
63
+ message: '`x.types === T` holds only if T is the *only* type.'
64
+ }
65
+ ```
66
+
67
+ | field | |
68
+ | :-- | :-- |
69
+ | `selector` | what to match, required |
70
+ | `replace` | the replacement, required. `{{name}}` is a capture's source text, `{{name\|last}}` keeps the part behind its last dot, turning `VertexType.Use` into `Use` |
71
+ | `capture` | name to a path relative to the match |
72
+ | `declaredIn` | path to a file the node's symbol must come from, so an unrelated `types` or `tag` does not match. The declaring file itself is exempt |
73
+ | `sameText` | pairs of paths that must spell the same code, esquery has no back-references |
74
+ | `message` | what to tell the reader, filled from the captures like `replace` |
75
+ | `fix` | `true` always fixes, `false` always suggests, which is right when the replacement can change type narrowing. Omitted, it fixes when the replacement is in scope |
76
+ | `id` | names the pattern in `@lintIgnore` and in the message |
77
+
78
+ An own `patterns` array replaces the defaults rather than merging with them. To propose one, open an issue with the
79
+ [replacement pattern template](.github/ISSUE_TEMPLATE/replacement-pattern.yaml).
80
+
81
+ ### Suppressing
82
+
83
+ | | silences |
84
+ | :-- | :-- |
85
+ | `// eslint-disable-next-line` | the next line, as usual |
86
+ | `@performanceCritical` | a hot path that has to keep the raw form |
87
+ | `@lintIgnore <ids>` | the named rules or pattern ids, all of them when given none |
88
+
89
+ A tag on a declaration covers everything below it, a header comment above the imports covers the file. The lookup is
90
+ guarded by a single string search, so files without either tag pay nothing.
91
+
92
+ ## A part of unicorn
93
+
94
+ A hand-picked part of [unicorn](https://github.com/sindresorhus/eslint-plugin-unicorn) is enabled as well: the rules
95
+ naming a shape with a clearer equivalent (`prefer-includes`, `prefer-string-slice`, `no-useless-spread`, and friends),
96
+ not its opinions on naming or style. The list is in [`index.ts`](index.ts). Three are left out on purpose:
97
+
98
+ - `prefer-array-flat` rewrites `.flatMap(f => f)` on an iterator, which has no `.flat()`.
99
+ - `prefer-structured-clone` does not know that a `JSON` round-trip is sometimes the point.
100
+ - `prefer-node-protocol` is inverted: `no-restricted-imports` forbids the `node:` prefix instead, because flowR is
101
+ bundled for the browser too, where the core modules are swapped for polyfills by their bare name (`path` to
102
+ `path-browserify`, `fs` to `false`). A `node:` specifier matches none of those bundler keys.
103
+
104
+ ## Working on it
105
+
106
+ The sources are TypeScript (`index.ts`, `patterns.ts`, `rules/*.ts`) and compile in place to the CommonJS files that
107
+ get published, so every import path a consumer uses stays what it was.
108
+
109
+ Node 24 or newer, the tests are TypeScript and run through Node's own type stripping.
110
+
111
+ ```shell
112
+ npm run build # tsc, what `prepublishOnly` runs
113
+ npm run typecheck # sources and tests, no emit
114
+ npm test # build, then `node --test` over the TypeScript tests
115
+ ```
package/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ declare const config: any[];
2
+ export = config;
package/index.js CHANGED
@@ -1,191 +1,214 @@
1
- const js = require('@eslint/js');
2
- const globals = require('globals');
3
- const tseslint = require('@typescript-eslint/eslint-plugin');
4
- const tsParser = require('@typescript-eslint/parser');
5
- const stylistic = require('@stylistic/eslint-plugin');
6
- const jsdoc = require('eslint-plugin-jsdoc');
7
- const tsdoc = require('eslint-plugin-tsdoc');
8
- const unusedImports = require('eslint-plugin-unused-imports');
9
- const checkFile = require('eslint-plugin-check-file');
10
- const importX = require('eslint-plugin-import-x');
11
-
12
- module.exports = [
13
- js.configs.recommended,
14
- ...tseslint.configs['flat/recommended-type-checked'],
15
- ...tseslint.configs['flat/strict'],
16
- ...tseslint.configs['flat/recommended'],
17
- jsdoc.configs['flat/recommended-typescript'],
18
- {
19
- languageOptions: {
20
- ecmaVersion: 'latest',
21
- sourceType: 'module',
22
- globals: { ...globals.node },
23
- parser: tsParser,
24
- parserOptions: {
25
- projectService: true
26
- }
27
- },
28
- settings: {
29
- 'import-x/resolver-next': [
30
- importX.createNodeResolver({ extensions: ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs'] })
31
- ]
32
- },
33
- plugins: {
34
- '@typescript-eslint': tseslint,
35
- '@stylistic': stylistic,
36
- 'import-x': importX,
37
- 'unused-imports': unusedImports,
38
- 'check-file': checkFile,
39
- tsdoc,
40
- jsdoc
41
- },
42
- rules: {
43
- '@stylistic/object-curly-spacing': ['error', 'always', { emptyObjects: 'never' }],
44
- '@stylistic/comma-spacing': ['error', { before: false, after: true }],
45
- '@stylistic/indent': ['error', 'tab', {
46
- FunctionDeclaration: { parameters: 'first' },
47
- ObjectExpression: 1,
48
- SwitchCase: 1
49
- }],
50
- '@stylistic/padding-line-between-statements': [
51
- 'error',
52
- { blankLine: 'never', prev: 'import', next: 'import' }
53
- ],
54
- '@stylistic/comma-dangle': ['error', 'only-multiline'],
55
- '@stylistic/quotes': ['error', 'single', { avoidEscape: true }],
56
-
57
- 'jsdoc/check-alignment': 'error',
58
- 'jsdoc/check-indentation': 'off',
59
- 'jsdoc/no-types': 'error',
60
- 'jsdoc/no-undefined-types': 'off',
61
- 'jsdoc/check-tag-names': 'off',
62
- 'jsdoc/require-param': 'off',
63
- 'jsdoc/require-description': 'off',
64
- 'jsdoc/require-param-description': 'off',
65
- 'jsdoc/require-returns': 'off',
66
- 'jsdoc/require-property': 'off',
67
- 'jsdoc/require-throws': 'off',
68
- 'jsdoc/require-throws-type': 'off',
69
- 'jsdoc/require-file-overview': 'off',
70
- 'jsdoc/check-param-names': 'off',
71
- 'jsdoc/require-jsdoc': ['error', {
72
- publicOnly: true,
73
- checkGetters: false,
74
- checkSetters: false,
75
- require: { FunctionDeclaration: true }
76
- }],
77
- 'jsdoc/require-param-type': 'off',
78
- 'jsdoc/require-returns-type': 'off',
79
- 'jsdoc/require-property-type': 'off',
80
- 'jsdoc/require-yields-type': 'off',
81
- 'jsdoc/require-next-type': 'off',
82
- 'jsdoc/require-yields': 'off',
83
-
84
- '@stylistic/no-mixed-spaces-and-tabs': ['error', 'smart-tabs'],
85
- 'no-warning-comments': ['error', {
86
- terms: ['todo', 'fixme', 'xxx'],
87
- location: 'anywhere'
88
- }],
89
-
90
- '@typescript-eslint/non-nullable-type-assertion-style': 'off',
91
- '@typescript-eslint/no-unsafe-enum-comparison': 'off',
92
- '@typescript-eslint/no-redundant-type-constituents': 'off',
93
- '@typescript-eslint/consistent-type-assertions': ['error', { assertionStyle: 'as' }],
94
-
95
- '@stylistic/key-spacing': ['error', { align: 'value' }],
96
- '@stylistic/no-tabs': ['error', { allowIndentationTabs: true }],
97
- '@stylistic/semi': ['error', 'always', { omitLastInOneLineBlock: true }],
98
- '@stylistic/space-before-function-paren': ['error', 'never'],
99
- '@stylistic/keyword-spacing': ['error', {
100
- before: true,
101
- after: true,
102
- overrides: {
103
- if: { after: false },
104
- for: { after: false },
105
- while: { after: false },
106
- do: { after: false },
107
- catch: { after: false },
108
- switch: { after: false },
109
- default: { after: false },
110
- throw: { after: false }
111
- }
112
- }],
113
-
114
- 'check-file/filename-naming-convention': ['error', {
115
- '**/*.ts': '?(\\d+-)?([A-Z])+([a-z])*((-|.)?([A-Z])+([a-z]))'
116
- }],
117
- 'check-file/folder-match-with-fex': ['error', {
118
- '*.spec.{js,jsx,ts,tsx}': 'test/**'
119
- }],
120
-
121
- '@stylistic/no-trailing-spaces': 'error',
122
- '@stylistic/space-infix-ops': ['error', { int32Hint: false }],
123
-
124
- '@typescript-eslint/no-unused-vars': 'off',
125
- 'unused-imports/no-unused-imports': 'error',
126
- 'unused-imports/no-unused-vars': ['error', {
127
- vars: 'all',
128
- varsIgnorePattern: '^_',
129
- args: 'after-used',
130
- argsIgnorePattern: '^_'
131
- }],
132
-
133
- 'tsdoc/syntax': 'error',
134
-
135
- '@typescript-eslint/naming-convention': ['error',
136
- {
137
- selector: 'variable',
138
- modifiers: ['const', 'global', 'exported'],
139
- format: ['camelCase', 'PascalCase', 'UPPER_CASE'],
140
- leadingUnderscore: 'allow',
141
- trailingUnderscore: 'allow'
142
- },
143
- {
144
- selector: 'variable',
145
- modifiers: ['const'],
146
- format: ['camelCase', 'PascalCase'],
147
- leadingUnderscore: 'allow',
148
- trailingUnderscore: 'allow'
149
- },
150
- {
151
- selector: 'enumMember',
152
- format: ['StrictPascalCase'],
153
- leadingUnderscore: 'forbid',
154
- trailingUnderscore: 'forbid'
155
- },
156
- {
157
- selector: 'typeLike',
158
- format: ['PascalCase']
159
- }
160
- ],
161
- '@typescript-eslint/consistent-type-imports': ['error', {
162
- prefer: 'type-imports',
163
- disallowTypeAnnotations: false,
164
- fixStyle: 'separate-type-imports'
165
- }],
166
- '@typescript-eslint/no-import-type-side-effects': 'error',
167
- '@typescript-eslint/consistent-type-exports': 'error',
168
-
169
- curly: 'error',
170
-
171
- '@stylistic/type-annotation-spacing': ['error', {
172
- before: false,
173
- after: true,
174
- overrides: { arrow: 'ignore' }
175
- }],
176
- '@stylistic/arrow-spacing': ['error', { before: true, after: true }],
177
- '@stylistic/brace-style': ['error', '1tbs'],
178
- '@stylistic/new-parens': 'error',
179
-
180
- 'import-x/no-duplicates': ['error', {
181
- considerQueryString: true,
182
- 'prefer-inline': false
183
- }],
184
-
185
- '@typescript-eslint/no-invalid-void-type': ['error', {
186
- allowInGenericTypeArguments: true,
187
- allowAsThisParameter: true
188
- }]
189
- }
190
- }
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ const js_1 = __importDefault(require("@eslint/js"));
6
+ const globals_1 = __importDefault(require("globals"));
7
+ const eslint_plugin_1 = __importDefault(require("@typescript-eslint/eslint-plugin"));
8
+ const parser_1 = __importDefault(require("@typescript-eslint/parser"));
9
+ const eslint_plugin_2 = __importDefault(require("@stylistic/eslint-plugin"));
10
+ const eslint_plugin_jsdoc_1 = __importDefault(require("eslint-plugin-jsdoc"));
11
+ const eslint_plugin_tsdoc_1 = __importDefault(require("eslint-plugin-tsdoc"));
12
+ const eslint_plugin_unused_imports_1 = __importDefault(require("eslint-plugin-unused-imports"));
13
+ const eslint_plugin_check_file_1 = __importDefault(require("eslint-plugin-check-file"));
14
+ const eslint_plugin_import_x_1 = __importDefault(require("eslint-plugin-import-x"));
15
+ const eslint_plugin_unicorn_1 = __importDefault(require("eslint-plugin-unicorn"));
16
+ const plugin_1 = __importDefault(require("./plugin"));
17
+ const patterns_1 = __importDefault(require("./patterns"));
18
+ const config = [
19
+ js_1.default.configs.recommended,
20
+ ...eslint_plugin_1.default.configs['flat/recommended-type-checked'],
21
+ ...eslint_plugin_1.default.configs['flat/strict'],
22
+ ...eslint_plugin_1.default.configs['flat/recommended'],
23
+ eslint_plugin_jsdoc_1.default.configs['flat/recommended-typescript'],
24
+ {
25
+ languageOptions: {
26
+ ecmaVersion: 'latest',
27
+ sourceType: 'module',
28
+ globals: { ...globals_1.default.node },
29
+ parser: parser_1.default,
30
+ parserOptions: {
31
+ projectService: true
32
+ }
33
+ },
34
+ settings: {
35
+ 'import-x/resolver-next': [
36
+ eslint_plugin_import_x_1.default.createNodeResolver({ extensions: ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs'] })
37
+ ]
38
+ },
39
+ plugins: {
40
+ '@typescript-eslint': eslint_plugin_1.default,
41
+ '@stylistic': eslint_plugin_2.default,
42
+ 'import-x': eslint_plugin_import_x_1.default,
43
+ 'unused-imports': eslint_plugin_unused_imports_1.default,
44
+ 'check-file': eslint_plugin_check_file_1.default,
45
+ tsdoc: eslint_plugin_tsdoc_1.default,
46
+ jsdoc: eslint_plugin_jsdoc_1.default,
47
+ flowr: plugin_1.default,
48
+ unicorn: eslint_plugin_unicorn_1.default
49
+ },
50
+ rules: {
51
+ /* point at the helper object named by the `@useInstead` tag of a declaration */
52
+ 'flowr/use-instead': 'error',
53
+ /* the hand-written forms of the helper calls, see `patterns.js` */
54
+ 'flowr/replacement-pattern': ['error', { patterns: patterns_1.default }],
55
+ /* flowR is bundled for the browser as well, where the core modules are swapped for polyfills by their bare
56
+ * name (`path` to `path-browserify`, `fs` to `false`, ...). A `node:` prefix does not match those keys,
57
+ * so it would force every consumer to strip it again before bundling. */
58
+ 'no-restricted-imports': ['error', {
59
+ patterns: [{
60
+ group: ['node:*', 'node:*/*'],
61
+ message: 'Import the bare module (`path`, not `node:path`) so a bundler can swap it for a browser polyfill.'
62
+ }]
63
+ }],
64
+ /* a hand-picked part of unicorn: the shapes with a clearer equivalent, not its opinions on naming or style */
65
+ ...Object.fromEntries([
66
+ 'prefer-array-some', 'prefer-array-index-of', 'prefer-includes',
67
+ /* `prefer-array-flat` is left out: it rewrites `.flatMap(f => f)` on an iterator, which has no `.flat()` */
68
+ 'no-unnecessary-array-flat-depth',
69
+ 'prefer-string-slice', 'prefer-string-starts-ends-with', 'prefer-regexp-test',
70
+ 'prefer-set-size', 'prefer-native-coercion-functions',
71
+ 'no-useless-spread', 'no-useless-length-check', 'no-array-push-push',
72
+ 'no-instanceof-builtins', 'no-single-promise-in-promise-methods',
73
+ 'throw-new-error', 'error-message', 'prefer-optional-catch-binding'
74
+ ].map(rule => [`unicorn/${rule}`, 'error'])),
75
+ '@stylistic/object-curly-spacing': ['error', 'always', { emptyObjects: 'never' }],
76
+ '@stylistic/comma-spacing': ['error', { before: false, after: true }],
77
+ '@stylistic/indent': ['error', 'tab', {
78
+ FunctionDeclaration: { parameters: 'first' },
79
+ ObjectExpression: 1,
80
+ SwitchCase: 1
81
+ }],
82
+ '@stylistic/padding-line-between-statements': [
83
+ 'error',
84
+ { blankLine: 'never', prev: 'import', next: 'import' }
85
+ ],
86
+ '@stylistic/comma-dangle': ['error', 'only-multiline'],
87
+ '@stylistic/quotes': ['error', 'single', { avoidEscape: true }],
88
+ 'jsdoc/check-alignment': 'error',
89
+ 'jsdoc/check-indentation': 'off',
90
+ 'jsdoc/no-types': 'error',
91
+ 'jsdoc/no-undefined-types': 'off',
92
+ 'jsdoc/check-tag-names': 'off',
93
+ 'jsdoc/require-param': 'off',
94
+ 'jsdoc/require-description': 'off',
95
+ 'jsdoc/require-param-description': 'off',
96
+ 'jsdoc/require-returns': 'off',
97
+ 'jsdoc/require-property': 'off',
98
+ 'jsdoc/require-throws': 'off',
99
+ 'jsdoc/require-throws-type': 'off',
100
+ 'jsdoc/require-file-overview': 'off',
101
+ 'jsdoc/check-param-names': 'off',
102
+ 'jsdoc/require-jsdoc': ['error', {
103
+ publicOnly: true,
104
+ checkGetters: false,
105
+ checkSetters: false,
106
+ require: { FunctionDeclaration: true }
107
+ }],
108
+ 'jsdoc/require-param-type': 'off',
109
+ 'jsdoc/require-returns-type': 'off',
110
+ 'jsdoc/require-property-type': 'off',
111
+ 'jsdoc/require-yields-type': 'off',
112
+ 'jsdoc/require-next-type': 'off',
113
+ 'jsdoc/require-yields': 'off',
114
+ '@stylistic/no-mixed-spaces-and-tabs': ['error', 'smart-tabs'],
115
+ 'no-warning-comments': ['error', {
116
+ terms: ['todo', 'fixme', 'xxx'],
117
+ location: 'anywhere'
118
+ }],
119
+ '@typescript-eslint/non-nullable-type-assertion-style': 'off',
120
+ '@typescript-eslint/no-unsafe-enum-comparison': 'off',
121
+ '@typescript-eslint/no-redundant-type-constituents': 'off',
122
+ '@typescript-eslint/consistent-type-assertions': ['error', { assertionStyle: 'as' }],
123
+ '@typescript-eslint/no-unnecessary-type-assertion': ['error', { typesToIgnore: ['never'] }],
124
+ /* bit-flag enums may spell their members as shifts, so the bit is the number you read */
125
+ '@typescript-eslint/prefer-literal-enum-member': ['error', { allowBitwiseExpressions: true }],
126
+ '@stylistic/key-spacing': ['error', { align: 'value' }],
127
+ '@stylistic/no-tabs': ['error', { allowIndentationTabs: true }],
128
+ '@stylistic/semi': ['error', 'always', { omitLastInOneLineBlock: true }],
129
+ '@stylistic/space-before-function-paren': ['error', 'never'],
130
+ '@stylistic/keyword-spacing': ['error', {
131
+ before: true,
132
+ after: true,
133
+ overrides: {
134
+ if: { after: false },
135
+ for: { after: false },
136
+ while: { after: false },
137
+ do: { after: false },
138
+ catch: { after: false },
139
+ switch: { after: false },
140
+ default: { after: false },
141
+ throw: { after: false }
142
+ }
143
+ }],
144
+ 'check-file/filename-naming-convention': ['error', {
145
+ '**/*.ts': '?(\\d+-)?([A-Z])+([a-z])*((-|.)?([A-Z])+([a-z]))'
146
+ }],
147
+ 'check-file/folder-match-with-fex': ['error', {
148
+ '*.spec.{js,jsx,ts,tsx}': 'test/**'
149
+ }],
150
+ '@stylistic/no-trailing-spaces': 'error',
151
+ '@stylistic/space-infix-ops': ['error', { int32Hint: false }],
152
+ '@typescript-eslint/no-unused-vars': 'off',
153
+ 'unused-imports/no-unused-imports': 'error',
154
+ 'unused-imports/no-unused-vars': ['error', {
155
+ vars: 'all',
156
+ varsIgnorePattern: '^_',
157
+ args: 'after-used',
158
+ argsIgnorePattern: '^_'
159
+ }],
160
+ 'tsdoc/syntax': 'error',
161
+ '@typescript-eslint/naming-convention': ['error',
162
+ {
163
+ selector: 'variable',
164
+ modifiers: ['const', 'global', 'exported'],
165
+ format: ['camelCase', 'PascalCase', 'UPPER_CASE'],
166
+ leadingUnderscore: 'allow',
167
+ trailingUnderscore: 'allow'
168
+ },
169
+ {
170
+ selector: 'variable',
171
+ modifiers: ['const'],
172
+ format: ['camelCase', 'PascalCase'],
173
+ leadingUnderscore: 'allow',
174
+ trailingUnderscore: 'allow'
175
+ },
176
+ {
177
+ selector: 'enumMember',
178
+ format: ['StrictPascalCase'],
179
+ leadingUnderscore: 'forbid',
180
+ trailingUnderscore: 'forbid'
181
+ },
182
+ {
183
+ selector: 'typeLike',
184
+ format: ['PascalCase']
185
+ }
186
+ ],
187
+ '@typescript-eslint/consistent-type-imports': ['error', {
188
+ prefer: 'type-imports',
189
+ disallowTypeAnnotations: false,
190
+ fixStyle: 'separate-type-imports'
191
+ }],
192
+ '@typescript-eslint/no-import-type-side-effects': 'error',
193
+ '@typescript-eslint/consistent-type-exports': 'error',
194
+ curly: 'error',
195
+ '@stylistic/type-annotation-spacing': ['error', {
196
+ before: false,
197
+ after: true,
198
+ overrides: { arrow: 'ignore' }
199
+ }],
200
+ '@stylistic/arrow-spacing': ['error', { before: true, after: true }],
201
+ '@stylistic/brace-style': ['error', '1tbs'],
202
+ '@stylistic/new-parens': 'error',
203
+ 'import-x/no-duplicates': ['error', {
204
+ considerQueryString: true,
205
+ 'prefer-inline': false
206
+ }],
207
+ '@typescript-eslint/no-invalid-void-type': ['error', {
208
+ allowInGenericTypeArguments: true,
209
+ allowAsThisParameter: true
210
+ }]
211
+ }
212
+ }
191
213
  ];
214
+ module.exports = config;
package/package.json CHANGED
@@ -1,28 +1,48 @@
1
1
  {
2
2
  "name": "@eagleoutice/eslint-config-flowr",
3
- "version": "2.0.1",
3
+ "version": "2.1.1",
4
4
  "description": "Linting rules for flowr and friends",
5
5
  "license": "ISC",
6
6
  "main": "index.js",
7
7
  "files": [
8
- "index.js"
8
+ "index.js",
9
+ "index.d.ts",
10
+ "plugin.js",
11
+ "plugin.d.ts",
12
+ "patterns.js",
13
+ "patterns.d.ts",
14
+ "pattern-type.js",
15
+ "pattern-type.d.ts",
16
+ "rules/*.js",
17
+ "rules/*.d.ts",
18
+ "tsdoc.json"
9
19
  ],
10
20
  "scripts": {
21
+ "build": "tsc --project tsconfig.build.json",
22
+ "typecheck": "tsc --noEmit",
23
+ "test": "npm run build && node --test \"test/*.test.ts\"",
24
+ "prepack": "npm run build",
11
25
  "publish-npm": "npm publish --access public"
12
26
  },
13
27
  "dependencies": {
14
28
  "@eslint/js": "^10.0.1",
15
- "globals": "^17.7.0"
16
- },
17
- "peerDependencies": {
18
29
  "@stylistic/eslint-plugin": "^5.7.1",
19
30
  "@typescript-eslint/eslint-plugin": "^8.54.0",
20
31
  "@typescript-eslint/parser": "^8.54.0",
21
- "eslint": "^10.0.0",
22
32
  "eslint-plugin-check-file": "^3.3.1",
23
33
  "eslint-plugin-import-x": "^4.17.1",
24
34
  "eslint-plugin-jsdoc": "^62.5.0",
25
35
  "eslint-plugin-tsdoc": "^0.5.0",
26
- "eslint-plugin-unused-imports": "^4.3.0"
36
+ "eslint-plugin-unicorn": "^73.0.0",
37
+ "eslint-plugin-unused-imports": "^4.3.0",
38
+ "globals": "^17.7.0"
39
+ },
40
+ "peerDependencies": {
41
+ "eslint": "^10.0.0"
42
+ },
43
+ "types": "index.d.ts",
44
+ "devDependencies": {
45
+ "@types/node": "^24.0.0",
46
+ "typescript": "^5.9.3"
27
47
  }
28
48
  }
@@ -0,0 +1,18 @@
1
+ /** The shape of a `flowr/replacement-pattern` entry, see the README for what each field does. */
2
+ export interface ReplacementPattern {
3
+ /** identifies the pattern in `@lintIgnore` and in the message */
4
+ readonly id?: string;
5
+ /** esquery selector, the language `no-restricted-syntax` uses */
6
+ readonly selector: string;
7
+ readonly message?: string;
8
+ /** capture name to a path relative to the match */
9
+ readonly capture?: Readonly<Record<string, string>>;
10
+ /** replacement template, filled from the captures */
11
+ readonly replace: string;
12
+ /** path to a substring of the file its symbol must be declared in */
13
+ readonly declaredIn?: Readonly<Record<string, string>>;
14
+ /** pairs of paths that have to spell the same code */
15
+ readonly sameText?: readonly (readonly string[])[];
16
+ /** `true` forces the autofix, `false` forces a suggestion */
17
+ readonly fix?: boolean;
18
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/patterns.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Default patterns of `flowr/replacement-pattern`, see the README for the format.
3
+ * The helpers they point at live in flowR: `DfEdge` in `dataflow/graph/edge`, the vertex helpers in
4
+ * `dataflow/graph/vertex`, and `NoEdges` in `dataflow/graph/graph`.
5
+ */
6
+ import type { ReplacementPattern } from './pattern-type';
7
+ declare const patterns: readonly ReplacementPattern[];
8
+ export = patterns;