Skip to content

Commit

Permalink
feat(eslint-plugin-experience): add no-implicit-public
Browse files Browse the repository at this point in the history
  • Loading branch information
splincode committed Feb 27, 2024
1 parent 1b7d15e commit 96afa80
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 304 deletions.
15 changes: 11 additions & 4 deletions projects/eslint-plugin-experience/all.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,9 @@ module.exports = {
],
},
],
'@taiga-ui/experience/no-implicit-public': 'error',
'@taiga-ui/experience/no-private-esnext-fields': 'error',
'@taiga-ui/experience/no-simple-for-of': 'error',
'@taiga-ui/experience/prefer-self-destroy-service': 'error',
'@taiga-ui/experience/strict-tui-doc-example': 'error',
'@typescript-eslint/array-type': [
'error',
Expand Down Expand Up @@ -248,7 +248,16 @@ module.exports = {
],
'@typescript-eslint/explicit-member-accessibility': [
'error',
{accessibility: 'no-public'},
{
accessibility: 'explicit',
overrides: {
accessors: 'explicit',
constructors: 'no-public',
methods: 'explicit',
properties: 'explicit',
parameterProperties: 'explicit',
},
},
],
'@typescript-eslint/func-call-spacing': 'error',
'@typescript-eslint/lines-between-class-members': [
Expand Down Expand Up @@ -890,8 +899,6 @@ module.exports = {
'@angular-eslint/use-component-selector': 'off',
'@angular-eslint/use-component-view-encapsulation': 'off',
'@angular-eslint/use-injectable-provided-in': 'off',
'@taiga-ui/experience/no-assert-without-ng-dev-mode': 'off',
'@taiga-ui/experience/no-typeof': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/consistent-type-imports': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
Expand Down
4 changes: 1 addition & 3 deletions projects/eslint-plugin-experience/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,10 @@ module.exports = {
rules: {
'decorator-key-sort': require('./rules/decorator-key-sort'),
'injection-token-description': require('./rules/injection-token-description'),
'no-assert-without-ng-dev-mode': require('./rules/no-assert-without-ng-dev-mode'),
'no-deep-imports': require('./rules/no-deep-imports'),
'no-implicit-public': require('./rules/no-implicit-public'),
'no-private-esnext-fields': require('./rules/no-private-esnext-fields'),
'no-simple-for-of': require('./rules/no-simple-for-of'),
'no-typeof': require('./rules/no-typeof'),
'prefer-self-destroy-service': require('./rules/prefer-self-destroy-service'),
'strict-tui-doc-example': require('./rules/strict-tui-doc-example'),
},
};

This file was deleted.

106 changes: 106 additions & 0 deletions projects/eslint-plugin-experience/rules/no-implicit-public.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
const ESLintUtils = require('@typescript-eslint/utils').ESLintUtils;

/**
* @type {import('eslint').Rule.RuleModule}
*/
module.exports = {
create(context) {
return {
MethodDefinition: node => checkImplicitPublic(context, node),
PropertyDefinition: node => checkImplicitPublic(context, node),
TSParameterProperty: node => checkImplicitPublic(context, node),
};
},
meta: {
fixable: 'code',
schema: [],
type: 'problem',
},
};

/**
* @param {import('eslint').Rule.RuleContext} context
* @param {import('eslint').Rule.Node} node
*/
function checkImplicitPublic(context, node) {
const classRef = getClass(node);

if (!classRef || node.kind === 'constructor' || !!node?.accessibility) {
return;
}

const name = node?.key?.name || node?.parameter?.name;
const services = ESLintUtils.getParserServices(context);
const superClass = classRef?.superClass ?? null;
const implements = classRef?.implements ?? [];

let keyInheretedClass = false;
let keyImplementedInsideOtherInterface = false;

if (superClass) {
const type = services.getTypeAtLocation(superClass);

keyInheretedClass = type.symbol?.members.has(name) ?? false;
}

for (const implement of implements) {
const type = services.getTypeAtLocation(implement);

if (type.symbol?.members.has(name)) {
keyImplementedInsideOtherInterface = true;
break;
}
}

let range = node?.parameter?.range ?? node.key.range ?? node.range;

const hasPublicDecorators = getDecorators(node?.decorators ?? []).find(decorator =>
['Inject', 'Input', 'Output'].includes(decorator),
);

if (node.kind === 'set' || node.kind === 'get') {
const [start, end] = node.key.range;

range = [start - node.kind.length - 1, end - node.kind.length - 1];
} else if (node.kind === 'method' && node.key?.object?.name === 'Symbol') {
const [start, end] = range;

range = [range[0] - 1, range[1] - 1];
}

if (node.type === 'PropertyDefinition' && node.decorators.length > 0) {
const [, end] = node.decorators[node.decorators.length - 1]?.range;

range = [end + 1, end + 2];
}

const marked =
hasPublicDecorators || keyImplementedInsideOtherInterface || keyInheretedClass
? ` public `
: ` protected `;

context.report({
fix: fixer => fixer.insertTextBeforeRange(range, marked),
message: `${node.kind || 'property'} ${name} should be marked as ${marked.trim()}`,
node,
});
}

function getDecorators(decorators) {
return decorators
?.map(decorator => decorator?.expression?.callee?.name)
.filter(Boolean);
}

/**
* @param {import('eslint').Rule.Node} node
*/
function getClass(node) {
if (!!node.parent && node.parent?.type === 'ClassDeclaration') {
return node.parent;
} else if (!!node.parent) {
return getClass(node.parent);
}

return null;
}
69 changes: 0 additions & 69 deletions projects/eslint-plugin-experience/rules/no-typeof.js

This file was deleted.

Loading

0 comments on commit 96afa80

Please sign in to comment.