@dmitryrechkin/eslint-standard 1.1.0 → 1.1.2

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/eslint.config.mjs CHANGED
@@ -6,7 +6,8 @@ import stylisticPlugin from '@stylistic/eslint-plugin';
6
6
  import jsdocPlugin from 'eslint-plugin-jsdoc';
7
7
  import simpleImportSortPlugin from 'eslint-plugin-simple-import-sort';
8
8
  import perfectionistPlugin from 'eslint-plugin-perfectionist';
9
- import jsdocIndentPlugin from './eslint-plugin-jsdoc-indent.mjs';
9
+ import jsdocIndentPlugin from './src/plugins/jsdoc-indent.mjs';
10
+ import interfaceBracePlugin from './src/plugins/interface-brace.mjs';
10
11
 
11
12
  export default function ({
12
13
  tsconfigPath = './tsconfig.json',
@@ -37,6 +38,7 @@ export default function ({
37
38
  'simple-import-sort': simpleImportSortPlugin,
38
39
  'perfectionist': perfectionistPlugin,
39
40
  'jsdoc-indent': jsdocIndentPlugin,
41
+ 'interface-brace': interfaceBracePlugin,
40
42
  ...plugins,
41
43
  },
42
44
  rules: {
@@ -45,15 +47,21 @@ export default function ({
45
47
  '@typescript-eslint/no-explicit-any': 'off',
46
48
 
47
49
  // Original coding guidelines
48
- 'brace-style': ['error', 'allman', { allowSingleLine: true }],
50
+ 'brace-style': 'off', // Disabled in favor of @stylistic/brace-style
51
+ '@stylistic/brace-style': ['error', 'allman', { allowSingleLine: true }],
49
52
  indent: 'off', // Disabled to avoid conflicts with @stylistic/indent and our JSDoc plugin
50
53
  '@stylistic/indent': ['error', 'tab', { SwitchCase: 1 }],
51
- quotes: ['error', 'single'],
52
- semi: ['error', 'always'],
54
+ quotes: 'off', // Disabled in favor of @stylistic/quotes
55
+ '@stylistic/quotes': ['error', 'single'],
56
+ semi: 'off', // Disabled in favor of @stylistic/semi
57
+ '@stylistic/semi': ['error', 'always'],
53
58
  '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
54
- 'no-trailing-spaces': 'error',
55
- 'eol-last': ['error', 'always'],
56
- 'comma-dangle': ['error', 'never'],
59
+ 'no-trailing-spaces': 'off', // Disabled in favor of @stylistic/no-trailing-spaces
60
+ '@stylistic/no-trailing-spaces': 'error',
61
+ 'eol-last': 'off', // Disabled in favor of @stylistic/eol-last
62
+ '@stylistic/eol-last': ['error', 'always'],
63
+ 'comma-dangle': 'off', // Disabled in favor of @stylistic/comma-dangle
64
+ '@stylistic/comma-dangle': ['error', 'never'],
57
65
 
58
66
  // Original naming conventions
59
67
  '@typescript-eslint/naming-convention': [
@@ -147,6 +155,9 @@ export default function ({
147
155
  'jsdoc/check-indentation': 'off', // Disabled to avoid conflicts with our custom plugin
148
156
  'jsdoc/tag-lines': 'off', // Disabled to avoid conflicts with our custom plugin
149
157
  'jsdoc-indent/jsdoc-indent': ['error', { tabWidth: 4 }],
158
+
159
+ // Enhanced: Interface brace style
160
+ 'interface-brace/interface-brace-style': 'error',
150
161
 
151
162
  // Allow custom rules to be added
152
163
  ...rules,
package/package.json CHANGED
@@ -1,16 +1,19 @@
1
1
  {
2
2
  "name": "@dmitryrechkin/eslint-standard",
3
3
  "description": "This package provides a shared ESLint configuration which includes TypeScript support and a set of specific linting rules designed to ensure high-quality and consistent code style across projects.",
4
- "version": "1.1.0",
4
+ "version": "1.1.2",
5
5
  "main": "eslint.config.mjs",
6
+ "bin": {
7
+ "eslint-standard": "./src/cli/index.mjs"
8
+ },
6
9
  "files": [
7
10
  "eslint.config.mjs",
8
- "eslint-plugin-jsdoc-indent.mjs",
11
+ "src/",
9
12
  "README.md",
10
13
  "LICENSE"
11
14
  ],
12
15
  "scripts": {
13
- "postinstall": "echo 'Remember to install peer dependencies:\n npm install eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-unused-imports @stylistic/eslint-plugin eslint-plugin-jsdoc --save-dev\n bun add eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-unused-imports @stylistic/eslint-plugin eslint-plugin-jsdoc --dev'",
16
+ "postinstall": "node src/cli/postinstall.mjs",
14
17
  "package:publish": "npm publish --access public",
15
18
  "test": "npm run test:formatting",
16
19
  "test:formatting": "node tests/test-runner.js"
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync } from 'fs';
4
+ import { fileURLToPath } from 'url';
5
+ import { dirname, join, resolve } from 'path';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+
10
+ // Get peer dependencies
11
+ const packageJsonPath = join(__dirname, '../../package.json');
12
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
13
+ const peerDeps = packageJson.peerDependencies || {};
14
+
15
+ // Check if running from node_modules
16
+ const isInNodeModules = __dirname.includes('node_modules');
17
+ const projectRoot = isInNodeModules
18
+ ? resolve(__dirname, '../../../../')
19
+ : process.cwd();
20
+
21
+ // Read project's package.json
22
+ let projectPackageJson;
23
+ try {
24
+ projectPackageJson = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf8'));
25
+ } catch (error) {
26
+ console.error('❌ Could not read project package.json');
27
+ process.exit(1);
28
+ }
29
+
30
+ const allDeps = {
31
+ ...projectPackageJson.dependencies || {},
32
+ ...projectPackageJson.devDependencies || {}
33
+ };
34
+
35
+ console.log('🔍 Checking ESLint Standard peer dependencies...\n');
36
+
37
+ let missingDeps = [];
38
+ let outdatedDeps = [];
39
+
40
+ for (const [dep, requiredVersion] of Object.entries(peerDeps)) {
41
+ if (!allDeps[dep]) {
42
+ missingDeps.push(`${dep}@${requiredVersion}`);
43
+ console.log(`❌ Missing: ${dep} (required: ${requiredVersion})`);
44
+ } else {
45
+ console.log(`✅ Found: ${dep}@${allDeps[dep]}`);
46
+ // Simple version check - could be improved
47
+ if (!allDeps[dep].includes('^') && !allDeps[dep].includes('~') && allDeps[dep] !== requiredVersion) {
48
+ outdatedDeps.push(`${dep} (installed: ${allDeps[dep]}, required: ${requiredVersion})`);
49
+ }
50
+ }
51
+ }
52
+
53
+ console.log('\n📊 Summary:');
54
+ if (missingDeps.length === 0 && outdatedDeps.length === 0) {
55
+ console.log('✅ All peer dependencies are satisfied!');
56
+ } else {
57
+ if (missingDeps.length > 0) {
58
+ console.log(`\n❌ Missing ${missingDeps.length} dependencies:`);
59
+ console.log('Run: npx @dmitryrechkin/eslint-standard install-deps');
60
+ }
61
+ if (outdatedDeps.length > 0) {
62
+ console.log(`\n⚠️ ${outdatedDeps.length} dependencies may be outdated:`);
63
+ outdatedDeps.forEach(dep => console.log(` - ${dep}`));
64
+ }
65
+ process.exit(1);
66
+ }
67
+
68
+ export default function checkDeps() {
69
+ // Export for programmatic use
70
+ }
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+
3
+ const command = process.argv[2];
4
+
5
+ switch (command) {
6
+ case 'install-deps':
7
+ await import('./install-deps.mjs');
8
+ break;
9
+ case 'check-deps':
10
+ await import('./check-deps.mjs');
11
+ break;
12
+ case 'help':
13
+ case '--help':
14
+ case '-h':
15
+ case undefined:
16
+ console.log(`
17
+ @dmitryrechkin/eslint-standard CLI
18
+
19
+ Usage:
20
+ npx @dmitryrechkin/eslint-standard <command>
21
+
22
+ Commands:
23
+ install-deps Install all peer dependencies
24
+ check-deps Check if all peer dependencies are installed
25
+ help Show this help message
26
+
27
+ Examples:
28
+ npx @dmitryrechkin/eslint-standard install-deps
29
+ npx @dmitryrechkin/eslint-standard check-deps
30
+ `);
31
+ break;
32
+ default:
33
+ console.error(`Unknown command: ${command}`);
34
+ console.log('Run "npx @dmitryrechkin/eslint-standard help" for usage information');
35
+ process.exit(1);
36
+ }
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execSync } from 'child_process';
4
+ import { readFileSync, existsSync } from 'fs';
5
+ import { fileURLToPath } from 'url';
6
+ import { dirname, join } from 'path';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+
11
+ // Detect package manager
12
+ function detectPackageManager() {
13
+ if (existsSync('pnpm-lock.yaml')) return 'pnpm';
14
+ if (existsSync('yarn.lock')) return 'yarn';
15
+ if (existsSync('package-lock.json')) return 'npm';
16
+ if (existsSync('bun.lockb')) return 'bun';
17
+ return 'npm'; // default
18
+ }
19
+
20
+ // Get peer dependencies from package.json
21
+ const packageJsonPath = join(__dirname, '../../package.json');
22
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
23
+ const peerDeps = packageJson.peerDependencies || {};
24
+
25
+ // Build install command
26
+ const packageManager = detectPackageManager();
27
+ const deps = Object.entries(peerDeps).map(([name, version]) => `${name}@${version}`).join(' ');
28
+
29
+ const installCommands = {
30
+ npm: `npm install --save-dev ${deps}`,
31
+ pnpm: `pnpm add -D ${deps}`,
32
+ yarn: `yarn add -D ${deps}`,
33
+ bun: `bun add -d ${deps}`
34
+ };
35
+
36
+ const command = installCommands[packageManager];
37
+
38
+ console.log(`🔧 Installing ESLint Standard peer dependencies...`);
39
+ console.log(`📦 Detected package manager: ${packageManager}`);
40
+ console.log(`📋 Running: ${command}\n`);
41
+
42
+ try {
43
+ execSync(command, { stdio: 'inherit' });
44
+ console.log('\n✅ All peer dependencies installed successfully!');
45
+ } catch (error) {
46
+ console.error('\n❌ Failed to install dependencies. Please run manually:');
47
+ console.error(command);
48
+ process.exit(1);
49
+ }
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname } from 'path';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
8
+
9
+ // Don't run during local development
10
+ if (!__dirname.includes('node_modules')) {
11
+ process.exit(0);
12
+ }
13
+
14
+ console.log(`
15
+ ╔════════════════════════════════════════════════════════════════╗
16
+ ║ ║
17
+ ║ 🎉 Thanks for installing @dmitryrechkin/eslint-standard! ║
18
+ ║ ║
19
+ ╚════════════════════════════════════════════════════════════════╝
20
+
21
+ 📦 This package requires peer dependencies to work properly.
22
+
23
+ 🚀 Quick install (auto-detects your package manager):
24
+ ${'\x1b[36m'}npx @dmitryrechkin/eslint-standard install-deps${'\x1b[0m'}
25
+
26
+ 📋 Or install manually:
27
+ ${'\x1b[90m'}npm install -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-unused-imports @stylistic/eslint-plugin eslint-plugin-jsdoc eslint-plugin-simple-import-sort eslint-plugin-perfectionist${'\x1b[0m'}
28
+
29
+ 🔍 To check if all dependencies are installed:
30
+ ${'\x1b[36m'}npx @dmitryrechkin/eslint-standard check-deps${'\x1b[0m'}
31
+
32
+ 📚 Documentation: https://github.com/dmitryrechkin/eslint-standard
33
+ `);
34
+
35
+ // Run check-deps to show current status
36
+ try {
37
+ await import('./check-deps.mjs');
38
+ } catch (error) {
39
+ // Silent fail - don't break installation
40
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Custom ESLint plugin to enforce Allman brace style for TypeScript interfaces
3
+ */
4
+
5
+ const interfaceBraceRule = {
6
+ meta: {
7
+ type: 'layout',
8
+ docs: {
9
+ description: 'Enforce Allman brace style for TypeScript interfaces',
10
+ category: 'Stylistic Issues',
11
+ recommended: false
12
+ },
13
+ fixable: 'whitespace',
14
+ schema: []
15
+ },
16
+
17
+ create(context) {
18
+ const sourceCode = context.sourceCode || context.getSourceCode();
19
+
20
+ return {
21
+ TSInterfaceDeclaration(node) {
22
+ // Get the interface name token
23
+ const interfaceId = node.id;
24
+ const typeParams = node.typeParameters;
25
+ const extendsClause = node.extends;
26
+
27
+ // Find the opening brace
28
+ let tokenBeforeBrace;
29
+ if (node.heritage && node.heritage.length > 0) {
30
+ // Interface extends something
31
+ tokenBeforeBrace = node.heritage[node.heritage.length - 1];
32
+ } else if (extendsClause && extendsClause.length > 0) {
33
+ // Interface extends something (newer AST)
34
+ tokenBeforeBrace = extendsClause[extendsClause.length - 1];
35
+ } else if (typeParams) {
36
+ // Interface has type parameters
37
+ tokenBeforeBrace = typeParams;
38
+ } else {
39
+ // Simple interface
40
+ tokenBeforeBrace = interfaceId;
41
+ }
42
+
43
+ // Get the opening brace token
44
+ const openingBrace = sourceCode.getTokenAfter(tokenBeforeBrace, token => token.type === 'Punctuator' && token.value === '{');
45
+
46
+ if (!openingBrace) return;
47
+
48
+ // Check if there's a newline before the opening brace
49
+ const tokenBefore = sourceCode.getTokenBefore(openingBrace);
50
+ const textBetween = sourceCode.text.slice(tokenBefore.range[1], openingBrace.range[0]);
51
+
52
+ // Check if brace is on the same line
53
+ if (!textBetween.includes('\n')) {
54
+ context.report({
55
+ node: openingBrace,
56
+ message: 'Opening brace should be on a new line (Allman style)',
57
+ fix(fixer) {
58
+ // Get the base indentation of the interface declaration
59
+ const interfaceLine = sourceCode.getLines()[node.loc.start.line - 1];
60
+ const baseIndentMatch = interfaceLine.match(/^(\s*)/);
61
+ const baseIndent = baseIndentMatch ? baseIndentMatch[1] : '';
62
+
63
+ // Replace the space before the brace with a newline and proper indentation
64
+ return fixer.replaceTextRange(
65
+ [tokenBefore.range[1], openingBrace.range[0]],
66
+ '\n' + baseIndent
67
+ );
68
+ }
69
+ });
70
+ }
71
+ },
72
+
73
+ // Also handle TSTypeAliasDeclaration with object type
74
+ TSTypeAliasDeclaration(node) {
75
+ if (node.typeAnnotation && node.typeAnnotation.type === 'TSTypeLiteral') {
76
+ const typeParams = node.typeParameters;
77
+ const tokenBeforeBrace = typeParams ? typeParams : node.id;
78
+
79
+ // Find the equals sign
80
+ const equalsToken = sourceCode.getTokenAfter(tokenBeforeBrace, token => token.type === 'Punctuator' && token.value === '=');
81
+
82
+ if (!equalsToken) return;
83
+
84
+ // Get the opening brace token
85
+ const openingBrace = sourceCode.getTokenAfter(equalsToken, token => token.type === 'Punctuator' && token.value === '{');
86
+
87
+ if (!openingBrace) return;
88
+
89
+ // Check if there's a newline before the opening brace
90
+ const textBetween = sourceCode.text.slice(equalsToken.range[1], openingBrace.range[0]);
91
+
92
+ // Check if brace is on the same line
93
+ if (!textBetween.includes('\n')) {
94
+ context.report({
95
+ node: openingBrace,
96
+ message: 'Opening brace should be on a new line (Allman style)',
97
+ fix(fixer) {
98
+ // Get the base indentation
99
+ const typeLine = sourceCode.getLines()[node.loc.start.line - 1];
100
+ const baseIndentMatch = typeLine.match(/^(\s*)/);
101
+ const baseIndent = baseIndentMatch ? baseIndentMatch[1] : '';
102
+
103
+ // Replace the space before the brace with a newline and proper indentation
104
+ return fixer.replaceTextRange(
105
+ [equalsToken.range[1], openingBrace.range[0]],
106
+ ' =\n' + baseIndent
107
+ );
108
+ }
109
+ });
110
+ }
111
+ }
112
+ }
113
+ };
114
+ }
115
+ };
116
+
117
+ export default {
118
+ rules: {
119
+ 'interface-brace-style': interfaceBraceRule
120
+ }
121
+ };
@@ -46,43 +46,69 @@ const jsdocIndentRule = {
46
46
  const baseIndentMatch = commentLine.match(/^(\s*)/);
47
47
  const baseIndent = baseIndentMatch ? baseIndentMatch[1] : '';
48
48
 
49
- for (let i = 1; i < lines.length; i++) { // Skip first line, include last line for closing */
49
+ for (let i = 0; i < lines.length; i++) {
50
50
  const line = lines[i];
51
- const leadingSpaces = line.match(/^( +)/);
52
51
 
53
- if (leadingSpaces) {
54
- // Check if this is the closing line (ends with */)
55
- const isClosingLine = line.trim() === '*/';
52
+ // Skip the opening line (/**)
53
+ if (i === 0) continue;
54
+
55
+ // Handle different line types
56
+ const trimmedLine = line.trim();
57
+
58
+ // Lines without asterisks in JSDoc (like "Text" instead of "* Text")
59
+ if (trimmedLine !== '' && !trimmedLine.startsWith('*') && !trimmedLine.endsWith('*/')) {
60
+ // This line should have an asterisk added
61
+ const start = comment.range[0] + 2; // +2 for "/*"
62
+ let lineOffset = 0;
63
+ for (let j = 0; j < i; j++) {
64
+ lineOffset += lines[j].length + 1; // +1 for \n
65
+ }
66
+
67
+ context.report({
68
+ node: comment,
69
+ loc: {
70
+ start: sourceCode.getLocFromIndex(start + lineOffset),
71
+ end: sourceCode.getLocFromIndex(start + lineOffset + line.length)
72
+ },
73
+ message: `JSDoc comment should be properly aligned`,
74
+ fix(fixer) {
75
+ return fixer.replaceTextRange(
76
+ [start + lineOffset, start + lineOffset + line.length],
77
+ baseIndent + ' * ' + trimmedLine
78
+ );
79
+ }
80
+ });
81
+ } else if (trimmedLine === '' || trimmedLine.startsWith('*')) {
82
+ // Check indentation for lines with asterisks
83
+ const leadingMatch = line.match(/^(\s*)/);
84
+ if (!leadingMatch) continue;
56
85
 
57
- // Calculate correct indentation
86
+ const leadingSpaces = leadingMatch[1];
87
+ const isClosingLine = trimmedLine === '*/';
58
88
  const correctIndent = isClosingLine ? baseIndent : baseIndent + ' ';
59
89
 
60
90
  // Skip if already correct to avoid circular fixes
61
- if (isClosingLine && line.startsWith(correctIndent + '*/')) {
62
- continue;
63
- } else if (!isClosingLine && line.startsWith(correctIndent + '*')) {
64
- continue;
65
- }
91
+ if (leadingSpaces === correctIndent) continue;
66
92
 
67
93
  // Calculate the position in the original source
68
- const lineStart = comment.range[0] + 2; // +2 for "/*"
94
+ const start = comment.range[0] + 2; // +2 for "/*"
69
95
  let lineOffset = 0;
70
96
  for (let j = 0; j < i; j++) {
71
97
  lineOffset += lines[j].length + 1; // +1 for \n
72
98
  }
73
99
 
74
- const start = lineStart + lineOffset;
75
- const end = start + leadingSpaces[1].length;
100
+ const lineStart = start + lineOffset;
101
+ const lineEnd = lineStart + leadingSpaces.length;
76
102
 
77
103
  context.report({
78
104
  node: comment,
79
105
  loc: {
80
- start: sourceCode.getLocFromIndex(start),
81
- end: sourceCode.getLocFromIndex(end)
106
+ start: sourceCode.getLocFromIndex(lineStart),
107
+ end: sourceCode.getLocFromIndex(lineEnd)
82
108
  },
83
109
  message: `JSDoc comment should be properly aligned`,
84
110
  fix(fixer) {
85
- return fixer.replaceTextRange([start, end], correctIndent);
111
+ return fixer.replaceTextRange([lineStart, lineEnd], correctIndent);
86
112
  }
87
113
  });
88
114
  }