@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.
@@ -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
+ };
@@ -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
+ };
@@ -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
+ }