@tsslint/config 2.0.7 → 3.0.0-alpha.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.
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=eslint-types.js.map
@@ -0,0 +1,27 @@
1
+ import type * as TSSLint from '@tsslint/types';
2
+ import type * as ESLint from 'eslint';
3
+ import type { ESLintRulesConfig } from './eslint-types.js';
4
+ type Severity = boolean | 'error' | 'warn';
5
+ /**
6
+ * Converts an ESLint rules configuration to TSSLint rules.
7
+ *
8
+ * ⚠️ **Type definitions not generated**
9
+ *
10
+ * Please add `@tsslint/config` to `pnpm.onlyBuiltDependencies` in your `package.json` to allow the postinstall script to run.
11
+ *
12
+ * ```json
13
+ * {
14
+ * "pnpm": {
15
+ * "onlyBuiltDependencies": ["@tsslint/config"]
16
+ * }
17
+ * }
18
+ * ```
19
+ *
20
+ * After that, run `pnpm install` again to generate type definitions.
21
+ *
22
+ * If the type definitions become outdated, please run `npx tsslint-docgen` to update them.
23
+ */
24
+ export declare function importESLintRules(config: {
25
+ [K in keyof ESLintRulesConfig]: Severity | [Severity, ...ESLintRulesConfig[K]];
26
+ }, context?: Partial<ESLint.Rule.RuleContext>): Promise<TSSLint.Rules>;
27
+ export {};
package/lib/eslint.js ADDED
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.importESLintRules = importESLintRules;
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ const noop = () => { };
7
+ const plugins = {};
8
+ const loader = async (moduleName) => {
9
+ let mod;
10
+ try {
11
+ mod ??= require(moduleName);
12
+ }
13
+ catch { }
14
+ try {
15
+ mod ??= await import(moduleName);
16
+ }
17
+ catch { }
18
+ if (mod && 'default' in mod) {
19
+ return mod.default;
20
+ }
21
+ return mod;
22
+ };
23
+ /**
24
+ * Converts an ESLint rules configuration to TSSLint rules.
25
+ *
26
+ * ⚠️ **Type definitions not generated**
27
+ *
28
+ * Please add `@tsslint/config` to `pnpm.onlyBuiltDependencies` in your `package.json` to allow the postinstall script to run.
29
+ *
30
+ * ```json
31
+ * {
32
+ * "pnpm": {
33
+ * "onlyBuiltDependencies": ["@tsslint/config"]
34
+ * }
35
+ * }
36
+ * ```
37
+ *
38
+ * After that, run `pnpm install` again to generate type definitions.
39
+ *
40
+ * If the type definitions become outdated, please run `npx tsslint-docgen` to update them.
41
+ */
42
+ async function importESLintRules(config, context = {}) {
43
+ let convertRule;
44
+ try {
45
+ ({ convertRule } = await import('@tsslint/compat-eslint'));
46
+ }
47
+ catch {
48
+ throw new Error('Please install @tsslint/compat-eslint to use importESLintRules().');
49
+ }
50
+ const rules = {};
51
+ for (const [rule, severityOrOptions] of Object.entries(config)) {
52
+ let severity;
53
+ let options;
54
+ if (Array.isArray(severityOrOptions)) {
55
+ [severity, ...options] = severityOrOptions;
56
+ }
57
+ else {
58
+ severity = severityOrOptions;
59
+ options = [];
60
+ }
61
+ if (!severity) {
62
+ rules[rule] = noop;
63
+ continue;
64
+ }
65
+ const ruleModule = await loadRuleByKey(rule);
66
+ if (!ruleModule) {
67
+ throw new Error(`Failed to resolve rule "${rule}".`);
68
+ }
69
+ rules[rule] = convertRule(ruleModule, options, { id: rule, ...context }, severity === 'error'
70
+ ? 1
71
+ : severity === 'warn'
72
+ ? 0
73
+ : 3);
74
+ }
75
+ return rules;
76
+ }
77
+ function* resolveRuleKey(rule) {
78
+ const slashIndex = rule.indexOf('/');
79
+ if (slashIndex !== -1) {
80
+ let pluginName = rule.startsWith('@')
81
+ ? `${rule.slice(0, slashIndex)}/eslint-plugin`
82
+ : `eslint-plugin-${rule.slice(0, slashIndex)}`;
83
+ let ruleName = rule.slice(slashIndex + 1);
84
+ yield [pluginName, ruleName];
85
+ if (ruleName.indexOf('/') >= 0) {
86
+ pluginName += `-${ruleName.slice(0, ruleName.indexOf('/'))}`;
87
+ ruleName = ruleName.slice(ruleName.indexOf('/') + 1);
88
+ yield [pluginName, ruleName];
89
+ }
90
+ }
91
+ else {
92
+ yield [undefined, rule];
93
+ }
94
+ }
95
+ async function loadRuleByKey(rule) {
96
+ for (const resolved of resolveRuleKey(rule)) {
97
+ const ruleModule = await loadRule(...resolved);
98
+ if (ruleModule) {
99
+ return ruleModule;
100
+ }
101
+ }
102
+ }
103
+ async function loadRule(pluginName, ruleName) {
104
+ if (pluginName) {
105
+ plugins[pluginName] ??= loader(pluginName);
106
+ const plugin = await plugins[pluginName];
107
+ return plugin?.rules[ruleName];
108
+ }
109
+ let dir = __dirname;
110
+ while (true) {
111
+ const rulePath = path.join(dir, 'node_modules', 'eslint', 'lib', 'rules', `${ruleName}.js`);
112
+ if (fs.existsSync(rulePath)) {
113
+ return loader(rulePath);
114
+ }
115
+ const parentDir = path.resolve(dir, '..');
116
+ if (parentDir === dir) {
117
+ break;
118
+ }
119
+ dir = parentDir;
120
+ }
121
+ }
122
+ //# sourceMappingURL=eslint.js.map
@@ -16,7 +16,7 @@ function create(config, source = 'tsslint') {
16
16
  }
17
17
  }
18
18
  return diagnostics;
19
- }
19
+ },
20
20
  });
21
21
  function match(code) {
22
22
  if (matchCache.has(code)) {
@@ -48,7 +48,7 @@ function create(cmdOption, reportsUnusedComments) {
48
48
  : nextLineRules.length
49
49
  ? 'Ignore 1 issue in next line'
50
50
  : undefined,
51
- }
51
+ },
52
52
  };
53
53
  if (result) {
54
54
  result.entries.push(item);
@@ -108,8 +108,8 @@ function create(cmdOption, reportsUnusedComments) {
108
108
  };
109
109
  return {
110
110
  resolveDiagnostics(file, results) {
111
- if (!reportsUnusedComments &&
112
- !results.some(error => error.source === 'tsslint')) {
111
+ if (!reportsUnusedComments
112
+ && !results.some(error => error.source === 'tsslint')) {
113
113
  return results;
114
114
  }
115
115
  const comments = new Map();
package/lib/tsl.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { Rule } from '@tsslint/config';
2
+ import type * as TSL from 'tsl' with { 'resolution-mode': 'import' };
3
+ export declare function fromTSLRules(tslRules: TSL.Rule<unknown>[]): Record<string, Rule>;
package/lib/tsl.js ADDED
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fromTSLRules = fromTSLRules;
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ function fromTSLRules(tslRules) {
7
+ let dir = __dirname;
8
+ let tslDir;
9
+ while (true) {
10
+ const potential = path.join(dir, 'node_modules', 'tsl');
11
+ if (fs.existsSync(potential)) {
12
+ tslDir = potential;
13
+ break;
14
+ }
15
+ const parentDir = path.dirname(dir);
16
+ if (parentDir === dir) {
17
+ break;
18
+ }
19
+ dir = parentDir;
20
+ }
21
+ if (!tslDir) {
22
+ throw new Error('Failed to find tsl package in node_modules.');
23
+ }
24
+ const visitorEntriesFile = fs.readdirSync(tslDir).find(f => /^visitorEntries-.*\.js$/.test(f));
25
+ if (!visitorEntriesFile) {
26
+ throw new Error('Failed to find visitorEntries file in tsl package.');
27
+ }
28
+ const { getContextUtils, visitorEntries } = require(path.join(tslDir, visitorEntriesFile));
29
+ const rules = {};
30
+ for (const tslRule of tslRules) {
31
+ rules[tslRule.name] = convertTSLRule(tslRule, visitorEntries);
32
+ }
33
+ return rules;
34
+ function convertTSLRule(rule, visitorEntries) {
35
+ return ({ typescript: ts, file, program, report }) => {
36
+ const context1 = {
37
+ checker: program.getTypeChecker(),
38
+ rawChecker: program.getTypeChecker(),
39
+ sourceFile: file,
40
+ report: descriptor => {
41
+ if ('node' in descriptor) {
42
+ report(descriptor.message, descriptor.node.getStart(file), descriptor.node.getEnd())
43
+ .at(new Error(), 1);
44
+ }
45
+ else {
46
+ report(descriptor.message, descriptor.start, descriptor.end)
47
+ .at(new Error(), 1);
48
+ }
49
+ },
50
+ compilerOptions: program.getCompilerOptions(),
51
+ program,
52
+ utils: getContextUtils(() => program),
53
+ };
54
+ const context2 = { ...context1, data: rule.createData?.(context1) };
55
+ ts.forEachChild(file, function cb(node) {
56
+ const nodeType = visitorEntries.find(e => e[0] === node.kind)?.[1];
57
+ if (nodeType) {
58
+ // @ts-expect-error
59
+ rule.visitor[nodeType]?.(context2, node);
60
+ }
61
+ ts.forEachChild(node, cb);
62
+ if (nodeType) {
63
+ // @ts-expect-error
64
+ rule.visitor[`${nodeType}_exit`]?.(context2, node);
65
+ }
66
+ });
67
+ };
68
+ }
69
+ }
70
+ //# sourceMappingURL=tsl.js.map
@@ -0,0 +1,4 @@
1
+ export declare function generateTSLintTypes(nodeModulesDirs: string[], loader?: (mod: string) => Promise<any>): Promise<{
2
+ dts: string;
3
+ stats: Record<string, number>;
4
+ }>;
@@ -0,0 +1,299 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateTSLintTypes = generateTSLintTypes;
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ const tslint_1 = require("./tslint");
7
+ const variableNameRegex = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/;
8
+ async function generateTSLintTypes(nodeModulesDirs, loader = async (mod) => {
9
+ try {
10
+ return require(mod);
11
+ }
12
+ catch {
13
+ return await import(mod);
14
+ }
15
+ }) {
16
+ let indentLevel = 0;
17
+ let dts = '';
18
+ let defId = 0;
19
+ line(`export interface TSLintRulesConfig {`);
20
+ indentLevel++;
21
+ const visited = new Set();
22
+ const defs = new Map();
23
+ const stats = {};
24
+ const rulesDirectories = (0, tslint_1.getTSLintRulesDirectories)();
25
+ for (const [rawDir, rulesDir] of rulesDirectories) {
26
+ if (fs.existsSync(rulesDir)) {
27
+ const ruleFiles = fs.readdirSync(rulesDir);
28
+ stats[rawDir] = 0;
29
+ for (const ruleFile of ruleFiles) {
30
+ if (ruleFile.endsWith('Rule.js') || ruleFile.endsWith('Rule.ts')) {
31
+ const camelCaseName = ruleFile.slice(0, -'Rule.js'.length);
32
+ const ruleName = camelCaseName.replace(/[A-Z]/g, (c, i) => (i === 0 ? c.toLowerCase() : '-' + c.toLowerCase()));
33
+ if (!visited.has(ruleName)) {
34
+ visited.add(ruleName);
35
+ try {
36
+ const rule = (await loader(path.join(rulesDir, ruleFile))).Rule;
37
+ addRule(ruleName, rule.metadata, rawDir);
38
+ stats[rawDir]++;
39
+ }
40
+ catch (e) {
41
+ addRule(ruleName, undefined, rawDir, e);
42
+ stats[rawDir]++;
43
+ }
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
49
+ // 2. Scan TSLint core rules
50
+ for (const nodeModulesDir of nodeModulesDirs) {
51
+ const tslintDir = path.join(nodeModulesDir, 'tslint', 'lib', 'rules');
52
+ if (fs.existsSync(tslintDir)) {
53
+ const ruleFiles = fs.readdirSync(tslintDir);
54
+ stats['tslint'] = stats['tslint'] || 0;
55
+ for (const ruleFile of ruleFiles) {
56
+ if (ruleFile.endsWith('Rule.js')) {
57
+ const camelCaseName = ruleFile.replace('Rule.js', '');
58
+ const ruleName = camelCaseName.replace(/[A-Z]/g, (c, i) => (i === 0 ? c.toLowerCase() : '-' + c.toLowerCase()));
59
+ if (!visited.has(ruleName)) {
60
+ visited.add(ruleName);
61
+ const rule = (await loader(path.join(tslintDir, ruleFile))).Rule;
62
+ addRule(ruleName, rule.metadata);
63
+ stats['tslint']++;
64
+ }
65
+ }
66
+ }
67
+ }
68
+ }
69
+ indentLevel--;
70
+ line(`}`);
71
+ line(``);
72
+ for (const [typeName, typeString] of defs.values()) {
73
+ line(`type ${typeName} = ${typeString};`);
74
+ }
75
+ return { dts, stats };
76
+ function addRule(ruleName, metadata, rulesDir, error) {
77
+ if (metadata || rulesDir || error) {
78
+ line(`/**`);
79
+ if (rulesDir) {
80
+ line(` * @rulesDirectory ${rulesDir}`);
81
+ }
82
+ if (error) {
83
+ line(` * @error ${error.message || error.toString()}`);
84
+ }
85
+ if (metadata?.description) {
86
+ for (const lineText of metadata.description.trim().split('\n')) {
87
+ line(` * ${lineText.replace(/\*\//g, '* /')}`);
88
+ }
89
+ }
90
+ if (metadata?.descriptionDetails) {
91
+ line(` *`);
92
+ for (const lineText of metadata.descriptionDetails.trim().split('\n')) {
93
+ line(` * ${lineText.replace(/\*\//g, '* /')}`);
94
+ }
95
+ }
96
+ if (metadata?.rationale) {
97
+ line(` *`);
98
+ line(` * @rationale`);
99
+ for (const lineText of metadata.rationale.trim().split('\n')) {
100
+ line(` * ${lineText.replace(/\*\//g, '* /')}`);
101
+ }
102
+ }
103
+ if (metadata?.optionsDescription) {
104
+ line(` *`);
105
+ line(` * @options`);
106
+ for (const lineText of metadata.optionsDescription.trim().split('\n')) {
107
+ line(` * ${lineText.replace(/\*\//g, '* /')}`);
108
+ }
109
+ }
110
+ if (metadata?.optionExamples) {
111
+ line(` *`);
112
+ line(` * @example`);
113
+ for (const example of metadata.optionExamples) {
114
+ if (typeof example === 'string') {
115
+ for (const lineText of example.trim().split('\n')) {
116
+ line(` * ${lineText.replace(/\*\//g, '* /')}`);
117
+ }
118
+ }
119
+ else {
120
+ line(` * ${JSON.stringify(ruleName)}: ${JSON.stringify(example)}`);
121
+ }
122
+ }
123
+ }
124
+ if (metadata?.type) {
125
+ line(` *`);
126
+ line(` * @type ${metadata.type}`);
127
+ }
128
+ if (metadata?.typescriptOnly) {
129
+ line(` *`);
130
+ line(` * @typescriptOnly`);
131
+ }
132
+ line(` */`);
133
+ }
134
+ let optionsType;
135
+ const schema = metadata?.options;
136
+ if (schema) {
137
+ if (Array.isArray(schema)) {
138
+ const optionsTypes = [];
139
+ for (const item of schema) {
140
+ const itemType = parseSchema(schema, item, indentLevel);
141
+ optionsTypes.push(itemType);
142
+ }
143
+ optionsType = `[`;
144
+ optionsType += optionsTypes
145
+ .map(type => `(${type})?`)
146
+ .join(', ');
147
+ optionsType += `]`;
148
+ }
149
+ else {
150
+ optionsType = parseSchema(schema, schema, indentLevel);
151
+ }
152
+ }
153
+ if (optionsType) {
154
+ line(`'${ruleName}'?: ${optionsType},`);
155
+ }
156
+ else {
157
+ line(`'${ruleName}'?: any[],`);
158
+ }
159
+ }
160
+ function line(line) {
161
+ dts += indent(indentLevel) + line + '\n';
162
+ }
163
+ function parseSchema(schema, item, indentLevel) {
164
+ if (typeof item === 'object' && item !== null) {
165
+ if (item.$ref) {
166
+ const paths = item.$ref
167
+ .replace('#/items/', '#/')
168
+ .split('/').slice(1);
169
+ let current = schema;
170
+ for (const path of paths) {
171
+ try {
172
+ current = current[path];
173
+ }
174
+ catch {
175
+ current = undefined;
176
+ break;
177
+ }
178
+ }
179
+ if (current) {
180
+ let resolved = defs.get(current);
181
+ if (!resolved) {
182
+ resolved = [`Def${defId++}_${paths[paths.length - 1]}`, parseSchema(schema, current, 0)];
183
+ defs.set(current, resolved);
184
+ }
185
+ return resolved[0];
186
+ }
187
+ else {
188
+ console.error(`Failed to resolve schema path: ${item.$ref}`);
189
+ return 'unknown';
190
+ }
191
+ }
192
+ else if (Array.isArray(item)) {
193
+ return item.map(item => parseSchema(schema, item, indentLevel)).join(' | ');
194
+ }
195
+ else if (Array.isArray(item.type)) {
196
+ return item.type.map((type) => parseSchema(schema, type, indentLevel)).join(' | ');
197
+ }
198
+ else if (item.properties) {
199
+ let res = `{\n`;
200
+ indentLevel++;
201
+ const properties = item.properties;
202
+ const requiredArr = item.required ?? [];
203
+ for (const key in properties) {
204
+ const property = properties[key];
205
+ if (property.description) {
206
+ res += indent(indentLevel) + `/**\n`;
207
+ res += indent(indentLevel) + ` * ${property.description.replace(/\*\//g, '* /')}\n`;
208
+ res += indent(indentLevel) + ` */\n`;
209
+ }
210
+ const propertyType = parseSchema(schema, property, indentLevel);
211
+ const isRequired = requiredArr.includes(key);
212
+ if (!variableNameRegex.test(key)) {
213
+ res += indent(indentLevel) + `'${key}'${isRequired ? '' : '?'}: ${propertyType},\n`;
214
+ }
215
+ else {
216
+ res += indent(indentLevel) + `${key}${isRequired ? '' : '?'}: ${propertyType},\n`;
217
+ }
218
+ }
219
+ indentLevel--;
220
+ res += indent(indentLevel) + `}`;
221
+ if (item.additionalProperties) {
222
+ res += ` & `;
223
+ res += parseAdditionalProperties(schema, item.additionalProperties, indentLevel);
224
+ }
225
+ return res;
226
+ }
227
+ else if (Array.isArray(item.required)) {
228
+ let res = `{ `;
229
+ const propertiesType = [];
230
+ for (const key of item.required) {
231
+ const propertyType = `any`;
232
+ if (!variableNameRegex.test(key)) {
233
+ propertiesType.push(`'${key}': ${propertyType}`);
234
+ }
235
+ else {
236
+ propertiesType.push(`${key}: ${propertyType}`);
237
+ }
238
+ }
239
+ res += propertiesType.join(', ');
240
+ res += ` }`;
241
+ return res;
242
+ }
243
+ else if (item.const) {
244
+ return JSON.stringify(item.const);
245
+ }
246
+ else if (item.type === 'array') {
247
+ if (Array.isArray(item.items)) {
248
+ return `[${item.items.map((item) => parseSchema(schema, item, indentLevel)).join(', ')}]`;
249
+ }
250
+ if (item.items) {
251
+ return `(${parseSchema(schema, item.items, indentLevel)})[]`;
252
+ }
253
+ return `any[]`;
254
+ }
255
+ else if (item.enum) {
256
+ return item.enum.map((v) => JSON.stringify(v)).join(' | ');
257
+ }
258
+ else if (item.type) {
259
+ return parseSchema(schema, item.type, indentLevel);
260
+ }
261
+ else if (item.anyOf) {
262
+ return item.anyOf.map((item) => parseSchema(schema, item, indentLevel)).join(' | ');
263
+ }
264
+ else if (item.oneOf) {
265
+ return item.oneOf.map((item) => parseSchema(schema, item, indentLevel)).join(' | ');
266
+ }
267
+ }
268
+ else if (item === 'string' || item === 'boolean' || item === 'null' || item === 'number') {
269
+ return item;
270
+ }
271
+ else if (item === 'object') {
272
+ if (item.additionalProperties) {
273
+ return parseAdditionalProperties(schema, item.additionalProperties, indentLevel);
274
+ }
275
+ else {
276
+ return `{ [key: string]: unknown }`;
277
+ }
278
+ }
279
+ else if (item === 'integer') {
280
+ return 'number';
281
+ }
282
+ else if (item === 'array') {
283
+ return 'any[]';
284
+ }
285
+ return 'unknown';
286
+ }
287
+ function indent(indentLevel) {
288
+ return '\t'.repeat(indentLevel);
289
+ }
290
+ function parseAdditionalProperties(schema, item, indentLevel) {
291
+ if (item === true) {
292
+ return `{ [key: string]: unknown }`;
293
+ }
294
+ else {
295
+ return `{ [key: string]: ${parseSchema(schema, item, indentLevel)} }`;
296
+ }
297
+ }
298
+ }
299
+ //# sourceMappingURL=tslint-gen.js.map