@orion.ui/orion-linter 1.0.0
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/LICENSE +21 -0
- package/README.md +112 -0
- package/dist/configs/eslint.config.mjs +256 -0
- package/dist/configs/stylelint.config.mjs +78 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +75 -0
- package/dist/rules/async-suffix.js +121 -0
- package/dist/rules/class-name-match-filename.js +34 -0
- package/dist/rules/default-props-are-static-readonly.js +48 -0
- package/dist/rules/events-are-in-camel-case.js +140 -0
- package/dist/rules/force-dynamic-vue-imports-in-router.js +80 -0
- package/dist/rules/force-dynamic-vue-imports-in-services.js +160 -0
- package/dist/rules/get-set-adjacent.js +154 -0
- package/dist/rules/get-set-one-liner.js +156 -0
- package/dist/rules/no-api-in-entity.js +32 -0
- package/dist/rules/no-api-in-setup.js +31 -0
- package/dist/rules/no-entity-in-service.js +31 -0
- package/dist/rules/no-export-type-in-ts.js +36 -0
- package/dist/rules/popables-are-readonly.js +52 -0
- package/dist/rules/private-property-if-only-in-template.js +192 -0
- package/dist/rules/state-are-private-readonly.js +89 -0
- package/dist/rules/template-refs-are-readonly.js +52 -0
- package/dist/types.d.ts +10 -0
- package/dist/types.js +17 -0
- package/dist/utils.d.ts +29 -0
- package/dist/utils.js +66 -0
- package/package.json +66 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
function getClassProperties(classNode) {
|
|
2
|
+
return classNode.body.body.filter(x => x.type === 'PropertyDefinition');
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
meta: {
|
|
7
|
+
type: 'problem',
|
|
8
|
+
docs: { description: `check that props in Setup are static and readonly` },
|
|
9
|
+
fixable: 'code',
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
create: function (context) {
|
|
13
|
+
const fileName = context.getPhysicalFilename().split('/').reverse()[0];
|
|
14
|
+
const sourceCode = context.sourceCode;
|
|
15
|
+
|
|
16
|
+
if (fileName === 'BaseEntity.ts') return {};
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
ClassDeclaration(node) {
|
|
20
|
+
const stateDeclarations = getClassProperties(node).filter((propertyNode) => {
|
|
21
|
+
return !(propertyNode.key.name === 'selfState' && fileName.includes('Entity.ts')) && propertyNode.key.name.toLowerCase().includes('state');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
stateDeclarations.forEach((stateDeclaration) => {
|
|
25
|
+
if (!stateDeclaration || !/@Reactive/.test(sourceCode.getText(stateDeclaration))) return;
|
|
26
|
+
|
|
27
|
+
stateDeclaration.private = stateDeclaration.accessibility === 'private';
|
|
28
|
+
|
|
29
|
+
if (stateDeclaration.private && stateDeclaration.readonly) return;
|
|
30
|
+
|
|
31
|
+
const messages = [];
|
|
32
|
+
const fixers = [];
|
|
33
|
+
|
|
34
|
+
if (!stateDeclaration.private && !node.abstract) {
|
|
35
|
+
messages.push(`\`state\` declaration should be \`private\`.`);
|
|
36
|
+
fixers.push('private');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!stateDeclaration.readonly) {
|
|
40
|
+
messages.push(`\`state\` declaration should be \`readonly\`.`);
|
|
41
|
+
fixers.push('readonly');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (messages.length) {
|
|
45
|
+
context.report({
|
|
46
|
+
node: stateDeclaration,
|
|
47
|
+
message: `Oops !
|
|
48
|
+
${messages.join('\n')}`,
|
|
49
|
+
fix: (fixer) => {
|
|
50
|
+
const text = sourceCode.getText(stateDeclaration);
|
|
51
|
+
|
|
52
|
+
// Regex to capture @Decorator + modifiers + property name + assignment
|
|
53
|
+
const decoratorPattern = /^(\s*@\w+\s+)((?:private\s+|readonly\s+)*)(\w+)(\s*=.*)$/s;
|
|
54
|
+
const match = text.match(decoratorPattern);
|
|
55
|
+
|
|
56
|
+
if (match) {
|
|
57
|
+
const [, decoratorPart, existingModifiers, propertyName, valueAssignment] = match;
|
|
58
|
+
|
|
59
|
+
// Analyze existing modifiers
|
|
60
|
+
const hasReadonly = /readonly\s+/.test(existingModifiers);
|
|
61
|
+
const hasPrivate = /private\s+/.test(existingModifiers);
|
|
62
|
+
|
|
63
|
+
// Reconstruct text with correct order
|
|
64
|
+
let allModifiers = '';
|
|
65
|
+
if (hasPrivate || fixers.includes('private')) {
|
|
66
|
+
allModifiers += 'private ';
|
|
67
|
+
}
|
|
68
|
+
if (hasReadonly || fixers.includes('readonly')) {
|
|
69
|
+
allModifiers += 'readonly ';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const newText = decoratorPart + allModifiers + propertyName + valueAssignment;
|
|
73
|
+
|
|
74
|
+
return fixer.replaceTextRange(stateDeclaration.range, newText);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
// Fallback: original behavior if no decorator
|
|
78
|
+
return stateDeclaration.readonly
|
|
79
|
+
? fixer.insertTextBefore(stateDeclaration, fixers.join(' ') + ' ')
|
|
80
|
+
: fixer.insertTextBeforeRange(stateDeclaration.key.range, fixers.join(' ') + ' ');
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
function getClassProperties(classNode) {
|
|
2
|
+
return classNode.body.body.filter(x => x.type === 'PropertyDefinition');
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
meta: {
|
|
7
|
+
type: 'problem',
|
|
8
|
+
docs: { description: `check that template refs declared in Setup are readonly` },
|
|
9
|
+
fixable: 'code',
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
create: function (context) {
|
|
13
|
+
const sourceCode = context.sourceCode;
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
ClassDeclaration(node) {
|
|
17
|
+
function getRefsDeclarations(classNode) {
|
|
18
|
+
const templateRefsRegex = /ref<(Orion[^>]*|HTML\w*)>\(\)/;
|
|
19
|
+
return getClassProperties(classNode)
|
|
20
|
+
.filter(propertyNode => templateRefsRegex.test(sourceCode.getText(propertyNode)))
|
|
21
|
+
.map(propertyNode => ({
|
|
22
|
+
node: propertyNode,
|
|
23
|
+
name: propertyNode.key.name,
|
|
24
|
+
readonly: propertyNode.readonly,
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getRefsDeclarations(node).forEach((propertyNode) => {
|
|
29
|
+
const messages = [];
|
|
30
|
+
const nameIsMissingUnderscore = propertyNode.name.charAt(0) !== '_';
|
|
31
|
+
|
|
32
|
+
if (!nameIsMissingUnderscore && propertyNode.readonly) return;
|
|
33
|
+
|
|
34
|
+
if (nameIsMissingUnderscore) messages.push(`Template refs name should begin with '_'.`);
|
|
35
|
+
if (!propertyNode.readonly) messages.push(`Template refs should be \`readonly\`.`);
|
|
36
|
+
|
|
37
|
+
context.report({
|
|
38
|
+
node: propertyNode.node,
|
|
39
|
+
message: `Oops !
|
|
40
|
+
${messages.join('\n')}`,
|
|
41
|
+
fix: (fixer) => {
|
|
42
|
+
return fixer.replaceTextRange(
|
|
43
|
+
propertyNode.node.range,
|
|
44
|
+
sourceCode.getText(propertyNode.node).replace(/^((readonly)?\s?_*)(.*)/, 'readonly _$3'),
|
|
45
|
+
);
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type LocalRuleName = 'async-suffix' | 'no-api-in-entity' | 'no-api-in-setup' | 'no-entity-in-service' | 'no-export-type-in-ts' | 'template-refs-are-readonly' | 'default-props-are-static-readonly' | 'state-are-private-readonly' | 'class-name-match-filename' | 'popables-are-readonly' | 'force-dynamic-vue-imports-in-services' | 'force-dynamic-vue-imports-in-router' | 'get-set-one-liner' | 'get-set-adjacent' | 'events-are-in-camel-case';
|
|
2
|
+
export type ESLintRuleLevel = 'off' | 'warn' | 'error';
|
|
3
|
+
export type LocalRuleConfig = {
|
|
4
|
+
[K in LocalRuleName as `orion-rules/${K}`]?: ESLintRuleLevel;
|
|
5
|
+
};
|
|
6
|
+
export type RuleOverrideConfig = {
|
|
7
|
+
files?: string[];
|
|
8
|
+
rules?: LocalRuleConfig;
|
|
9
|
+
};
|
|
10
|
+
export declare const ORION_RULES: Record<LocalRuleName, string>;
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const ORION_RULES = {
|
|
2
|
+
'async-suffix': 'orion-rules/async-suffix',
|
|
3
|
+
'no-api-in-entity': 'orion-rules/no-api-in-entity',
|
|
4
|
+
'no-api-in-setup': 'orion-rules/no-api-in-setup',
|
|
5
|
+
'no-entity-in-service': 'orion-rules/no-entity-in-service',
|
|
6
|
+
'no-export-type-in-ts': 'orion-rules/no-export-type-in-ts',
|
|
7
|
+
'template-refs-are-readonly': 'orion-rules/template-refs-are-readonly',
|
|
8
|
+
'default-props-are-static-readonly': 'orion-rules/default-props-are-static-readonly',
|
|
9
|
+
'state-are-private-readonly': 'orion-rules/state-are-private-readonly',
|
|
10
|
+
'class-name-match-filename': 'orion-rules/class-name-match-filename',
|
|
11
|
+
'popables-are-readonly': 'orion-rules/popables-are-readonly',
|
|
12
|
+
'force-dynamic-vue-imports-in-services': 'orion-rules/force-dynamic-vue-imports-in-services',
|
|
13
|
+
'force-dynamic-vue-imports-in-router': 'orion-rules/force-dynamic-vue-imports-in-router',
|
|
14
|
+
'get-set-one-liner': 'orion-rules/get-set-one-liner',
|
|
15
|
+
'get-set-adjacent': 'orion-rules/get-set-adjacent',
|
|
16
|
+
'events-are-in-camel-case': 'orion-rules/events-are-in-camel-case',
|
|
17
|
+
};
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ESLintRuleLevel, LocalRuleName, RuleOverrideConfig } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a configuration to disable specific rules
|
|
4
|
+
* @param rules - List of rules to disable
|
|
5
|
+
* @returns ESLint configuration to disable rules
|
|
6
|
+
*/
|
|
7
|
+
export declare function disableRules(rules: LocalRuleName[]): RuleOverrideConfig;
|
|
8
|
+
/**
|
|
9
|
+
* Creates a configuration to define the error level for specific rules
|
|
10
|
+
* @param rules - Object with rules and their level
|
|
11
|
+
* @returns ESLint configuration for rules
|
|
12
|
+
*/
|
|
13
|
+
export declare function configureRules(rules: Partial<Record<LocalRuleName, ESLintRuleLevel>>): RuleOverrideConfig;
|
|
14
|
+
/**
|
|
15
|
+
* Creates a configuration for specific files
|
|
16
|
+
* @param files - File patterns
|
|
17
|
+
* @param rules - Rule configuration
|
|
18
|
+
* @returns ESLint configuration for specific files
|
|
19
|
+
*/
|
|
20
|
+
export declare function configureForFiles(files: string[], rules: Partial<Record<LocalRuleName, ESLintRuleLevel>>): RuleOverrideConfig;
|
|
21
|
+
/**
|
|
22
|
+
* List of all available rules with their full names
|
|
23
|
+
*/
|
|
24
|
+
export declare const AVAILABLE_RULES: Record<LocalRuleName, string>;
|
|
25
|
+
/**
|
|
26
|
+
* Creates a configuration to disable all Orion rules
|
|
27
|
+
* @returns ESLint configuration to disable all rules
|
|
28
|
+
*/
|
|
29
|
+
export declare function disableAllOrionRules(): RuleOverrideConfig;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ORION_RULES } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a configuration to disable specific rules
|
|
4
|
+
* @param rules - List of rules to disable
|
|
5
|
+
* @returns ESLint configuration to disable rules
|
|
6
|
+
*/
|
|
7
|
+
export function disableRules(rules) {
|
|
8
|
+
const rulesConfig = {};
|
|
9
|
+
rules.forEach(rule => {
|
|
10
|
+
rulesConfig[`orion-rules/${rule}`] = 'off';
|
|
11
|
+
});
|
|
12
|
+
return {
|
|
13
|
+
rules: rulesConfig
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Creates a configuration to define the error level for specific rules
|
|
18
|
+
* @param rules - Object with rules and their level
|
|
19
|
+
* @returns ESLint configuration for rules
|
|
20
|
+
*/
|
|
21
|
+
export function configureRules(rules) {
|
|
22
|
+
const rulesConfig = {};
|
|
23
|
+
Object.entries(rules).forEach(([rule, level]) => {
|
|
24
|
+
if (level) {
|
|
25
|
+
rulesConfig[`orion-rules/${rule}`] = level;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
return {
|
|
29
|
+
rules: rulesConfig
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Creates a configuration for specific files
|
|
34
|
+
* @param files - File patterns
|
|
35
|
+
* @param rules - Rule configuration
|
|
36
|
+
* @returns ESLint configuration for specific files
|
|
37
|
+
*/
|
|
38
|
+
export function configureForFiles(files, rules) {
|
|
39
|
+
const rulesConfig = {};
|
|
40
|
+
Object.entries(rules).forEach(([rule, level]) => {
|
|
41
|
+
if (level) {
|
|
42
|
+
rulesConfig[`orion-rules/${rule}`] = level;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
files,
|
|
47
|
+
rules: rulesConfig
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* List of all available rules with their full names
|
|
52
|
+
*/
|
|
53
|
+
export const AVAILABLE_RULES = ORION_RULES;
|
|
54
|
+
/**
|
|
55
|
+
* Creates a configuration to disable all Orion rules
|
|
56
|
+
* @returns ESLint configuration to disable all rules
|
|
57
|
+
*/
|
|
58
|
+
export function disableAllOrionRules() {
|
|
59
|
+
const rulesConfig = {};
|
|
60
|
+
Object.values(ORION_RULES).forEach(rule => {
|
|
61
|
+
rulesConfig[rule] = 'off';
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
rules: rulesConfig
|
|
65
|
+
};
|
|
66
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"name": "@orion.ui/orion-linter",
|
|
4
|
+
"description": "Linting based on Stylistic & Stylelint",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"author": "Orion UI <dev@orion-ui.org>",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"homepage": "https://github.com/orion-ui/orion-linter#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/orion-ui/orion-linter.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/orion-ui/orion-linter/issues"
|
|
15
|
+
},
|
|
16
|
+
"main": "dist/index.js",
|
|
17
|
+
"types": "dist/index.d.ts",
|
|
18
|
+
"exports": "./dist/index.js",
|
|
19
|
+
"files": [
|
|
20
|
+
"dist/",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "rimraf dist && tsc && npm run copy-configs",
|
|
25
|
+
"copy-configs": "cpy src/configs/**/* dist/configs && cpy src/rules/**/* dist/rules",
|
|
26
|
+
"prepack": "npm run build",
|
|
27
|
+
"prepublishOnly": "npm run build && npm run test:package",
|
|
28
|
+
"test:package": "node -e \"import('./dist/index.js').then(async pkg => { const config = await pkg.getESLintConfig(); console.log('Package test passed:', typeof config === 'object'); });\"",
|
|
29
|
+
"test": "node tests/rules/*.test.js"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"eslint",
|
|
33
|
+
"stylelint",
|
|
34
|
+
"linter",
|
|
35
|
+
"typescript",
|
|
36
|
+
"vue",
|
|
37
|
+
"css",
|
|
38
|
+
"less",
|
|
39
|
+
"frontend"
|
|
40
|
+
],
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18.12.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"eslint": "^9.0.0",
|
|
46
|
+
"stylelint": "^16.0.0",
|
|
47
|
+
"typescript": "^5.0.0"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@stylistic/eslint-plugin": "^5.2.0",
|
|
51
|
+
"@stylistic/stylelint-config": "^2.0.0",
|
|
52
|
+
"@typescript-eslint/eslint-plugin": "^8.37.0",
|
|
53
|
+
"@typescript-eslint/parser": "^8.37.0",
|
|
54
|
+
"eslint-plugin-vue": "^10.3.0",
|
|
55
|
+
"stylelint-config-clean-order": "^7.0.0",
|
|
56
|
+
"stylelint-config-recommended-vue": "^1.6.1",
|
|
57
|
+
"stylelint-config-standard-less": "^3.0.1"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@types/eslint": "^9.6.1",
|
|
61
|
+
"@types/node": "^20.0.0",
|
|
62
|
+
"cpy-cli": "^5.0.0",
|
|
63
|
+
"rimraf": "^6.0.1",
|
|
64
|
+
"typescript": "^5.8.3"
|
|
65
|
+
}
|
|
66
|
+
}
|