@dmitryrechkin/eslint-standard 1.1.1 → 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 +2 -2
- package/package.json +6 -3
- package/src/cli/check-deps.mjs +70 -0
- package/src/cli/index.mjs +36 -0
- package/src/cli/install-deps.mjs +49 -0
- package/src/cli/postinstall.mjs +40 -0
- package/src/plugins/interface-brace.mjs +121 -0
- /package/{eslint-plugin-jsdoc-indent.mjs → src/plugins/jsdoc-indent.mjs} +0 -0
package/eslint.config.mjs
CHANGED
|
@@ -6,8 +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 './
|
|
10
|
-
import interfaceBracePlugin from './
|
|
9
|
+
import jsdocIndentPlugin from './src/plugins/jsdoc-indent.mjs';
|
|
10
|
+
import interfaceBracePlugin from './src/plugins/interface-brace.mjs';
|
|
11
11
|
|
|
12
12
|
export default function ({
|
|
13
13
|
tsconfigPath = './tsconfig.json',
|
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.
|
|
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
|
-
"
|
|
11
|
+
"src/",
|
|
9
12
|
"README.md",
|
|
10
13
|
"LICENSE"
|
|
11
14
|
],
|
|
12
15
|
"scripts": {
|
|
13
|
-
"postinstall": "
|
|
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
|
+
};
|
|
File without changes
|