Skip to content

Commit

Permalink
Es linter (#94)
Browse files Browse the repository at this point in the history
* eslint

* new plugin

* delete files

* change

* Renamed folder + removed configs + removed fit

* Update snapshots temporarily

* add local rule to check dependencies

* --

* commit before merging from master

* Align dependencies + cleanup

* More lint fixes

* More fixes

* add name of the undefined

* change

* make all the files run

* split rule to different files

* change structure and naming

* update lock file

* f

* Upgrade to TSESLint 6.6.x + rename to undefined-dependency

* add simple test

* ignore node modules in lint

* -

* -

* Update ci.yml

* comment test

* change deps

* changes deps

* empty commit

* empty commit

* change deps

* update yarn

* update deps

* update deps

* update deps

* update yarn

* update ci

* change to yarn

* add build

---------

Co-authored-by: Guy Carmeli <[email protected]>
Co-authored-by: Guy Carmeli <>
  • Loading branch information
orlyd and guyca authored Feb 22, 2024
1 parent 9af79b0 commit c184335
Show file tree
Hide file tree
Showing 24 changed files with 4,515 additions and 4,117 deletions.
2 changes: 1 addition & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1 +1 @@
dist/*
dist/*,
17 changes: 8 additions & 9 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,23 @@
"airbnb-base",
"airbnb-typescript",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended",
// "plugin:@typescript-eslint/recommended-type-checked",
"plugin:import/typescript"
],
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json",
"ecmaVersion": 2021,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
"sourceType": "module",
"project": "./tsconfig.json"
},
"plugins": [
"react",
"@typescript-eslint",
"import-newlines",
"unused-imports"
"unused-imports",
"eslint-plugin-local-rules"
],
"rules": {
"no-console":"off",
"max-len": ["error", {"code": 115}],
"lines-between-class-members": ["error", "always", {"exceptAfterSingleLine": true}],
"import/extensions": "off",
Expand Down Expand Up @@ -90,6 +88,7 @@
"error",
{ "vars": "all", "varsIgnorePattern": "^_", "args": "after-used", "argsIgnorePattern": "^_" }
],
"local-rules/undefined-dependency":"error",
"@typescript-eslint/ban-ts-comment": "off"
},
"settings": {
Expand Down
8 changes: 5 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ jobs:
matrix:
node-version: [16.14.x]


env:
NPM_EMAIL: ''
NPM_TOKEN: ''
Expand All @@ -25,6 +26,7 @@ jobs:
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm run lint
- run: npm run test
- run: yarn install
- run: yarn build-local
- run: yarn lint
- run: yarn test
8 changes: 4 additions & 4 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version: 16
cache: npm
cache-dependency-path: documentation/package-lock.json
cache: yarn
cache-dependency-path: documentation/yarn.lock

- name: Install dependencies
working-directory: documentation
run: npm ci
run: yarn install --frozen-lockfile
- name: Build documentation
working-directory: documentation
run: npm run build
run: yarn build

- name: Setup EnhanceDocs
uses: enhancedocs/[email protected]
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/test-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version: 16
cache: npm
cache-dependency-path: documentation/package-lock.json
cache: yarn
cache-dependency-path: documentation/yarn.lock

- name: Install dependencies
working-directory: ./documentation
run: npm ci
run: yarn install --frozen-lockfile
- name: Test build documentation
working-directory: ./documentation
run: npm run build
run: yarn build
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,8 @@ buck-out/

main.jsbundle.map
index.android.bundle.map

.jest_cache
.eslintcache
**/.docusaurus
coverage/*
3 changes: 2 additions & 1 deletion .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ tsconfig.json
tsconfig.prod.json
global.d.ts
README.md
babel.config.js
babel.config.js
eslint-local-rules.js
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
14
16.20.0
5 changes: 5 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"recommendations": [
"adpyke.codesnap"
]
}
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"cSpell.words": [
"estree",
"TSES",
"unmagler"
"Middlewares",
"MVVM",
"preconfigured",
Expand Down
5 changes: 5 additions & 0 deletions eslint-local-rules.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const {undefinedDependencies} = require('./dist/eslint/undefinedDependencies/ruleConfiguration')

module.exports = {
'undefined-dependency': undefinedDependencies
};
115 changes: 115 additions & 0 deletions eslint/undefinedDependencies/ASTFunctions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { TSESTree } from '@typescript-eslint/typescript-estree';
import * as fs from 'fs';
import { parse } from '@typescript-eslint/parser';
import path= require('path') ;
import { TSESLint } from '@typescript-eslint/utils';

export type MessageIds = 'undefinedDependency';

export function getSubGraphs(decorators: TSESTree.Decorator[]) {
const args = (decorators[0].expression as TSESTree.CallExpression).arguments;
if (args) {
for (let i = 0; i < args.length; i++) {
if (args[i].type === TSESTree.AST_NODE_TYPES.ObjectExpression) {
const { properties } = (args[i] as TSESTree.ObjectExpression);
if (properties) {
for (let j = 0; j < properties.length; j++) {
if (((properties[j] as TSESTree.Property).key as TSESTree.Identifier).name === 'subgraphs') {
return ((properties[j] as TSESTree.Property).value as TSESTree.ArrayExpression)
.elements.map((subGraph) => (subGraph as TSESTree.Identifier).name);
}
}
}
}
}
}
return [];
}

export function getDependenciesFromSubgraphs(
imports: TSESTree.ImportDeclaration[],
subGraphs:string[],
context:Readonly<TSESLint.RuleContext<MessageIds, []>>,
) {
const paths:Record<string, string>[] = [];
const dependencies: string[] = [];
imports.forEach((el) => {
el.specifiers.forEach((specifier) => {
if (subGraphs.includes(specifier.local.name)) {
paths.push({ path: el.source.value, import: specifier.local.name });
}
});
});
paths.forEach((el) => {
const filePath = path.join(path.dirname(context.getFilename()), `${el['path']}.ts`);
const fileContent = fs.readFileSync(filePath, 'utf8');
const fileAST = parse(
fileContent,
{
ecmaVersion: 9,
ecmaFeatures: {
globalReturn: false,
jsx: true,
},
sourceType: 'module',
comment: true,
attachComment: true,
tokens: true,
loc: true,
range: true,
filePath,
},
);
dependencies.push(...mapFunctions(
(fileAST.body[fileAST.body.length - 1] as TSESTree.ExportDefaultDeclaration)
.declaration as TSESTree.ClassDeclaration,
));
});
return dependencies;
}
export function mapFunctions(node: TSESTree.ClassDeclaration) {
const { body } = node.body;
const existingDependencies: string[] = [];
body.forEach((el) => {
if (el.type === TSESTree.AST_NODE_TYPES.MethodDefinition) {
const decorators = (el)?.decorators;
if (decorators) {
if (decorators.map((decorator) => getDecoratorName(decorator)).includes('Provides')) {
existingDependencies.push(((el as TSESTree.MethodDefinition).key as TSESTree.Identifier).name);
}
}
}
});
return existingDependencies;
}
export function checkDependencies(node: TSESTree.ClassDeclaration, existingDependencies: string[]) {
const body = node?.body?.body;
for (let j = 0; j < body.length; j++) {
if (body[j].type === TSESTree.AST_NODE_TYPES.MethodDefinition
&& ((body[j] as TSESTree.MethodDefinition).key as TSESTree.Identifier).name !== 'constructor') {
const params = (body[j] as TSESTree.MethodDefinition).value?.params;
if (params) {
for (let i = 0; i < params.length; i++) {
if (!existingDependencies.includes((params[i] as TSESTree.Identifier).name)) {
return {
value: false,
param: (params[i] as TSESTree.Identifier).name,
};
}
}
}
}
}
return { value: true };
}
export function getDecoratorName(decorator: TSESTree.Decorator) {
return ((decorator?.expression as TSESTree.CallExpression)?.callee as TSESTree.Identifier)?.name;
}

export function getPropertyDeclarations(node:TSESTree.ClassDeclaration) {
const classBody = node.body.body;
const properties = classBody.map((method) => {
return ((method as (TSESTree.PropertyDefinition | TSESTree.MethodDefinition)).key as TSESTree.Identifier).name;
});
return properties;
}
45 changes: 45 additions & 0 deletions eslint/undefinedDependencies/createFunction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { TSESTree } from '@typescript-eslint/typescript-estree';

import {
getSubGraphs,
getDependenciesFromSubgraphs,
mapFunctions,
checkDependencies,
getDecoratorName,
getPropertyDeclarations,
} from './ASTFunctions';

export function create(context: any) {
const imports:TSESTree.ImportDeclaration[] = [];
const dependencies:string[] = [];

return {
ImportDeclaration(node:TSESTree.ImportDeclaration) {
imports.push(node);
},
ClassDeclaration(node:TSESTree.ClassDeclaration) {
const { decorators } = node;
if (decorators) {
const decoratorNames = decorators.map((decorator) => getDecoratorName(decorator));
if (decoratorNames.includes('Graph')) {
const subGraphs = getSubGraphs(decorators);
if (subGraphs.length > 0) {
dependencies.push(...getDependenciesFromSubgraphs(imports, subGraphs, context));
}
dependencies.push(...mapFunctions(node));
dependencies.push(...getPropertyDeclarations(node));
const check = checkDependencies(node, dependencies);
if (!check?.value) {
context.report({
node,
messageId: 'undefinedDependency',
data: {
dependencyName: check.param,
},
});
}
}
}
},
};
}
24 changes: 24 additions & 0 deletions eslint/undefinedDependencies/invalidGraphs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export const invalidGraph = `import { Graph, ObjectGraph, Provides } from 'src';
@Graph()
export default class SimpleGraph extends ObjectGraph {
@Provides()
instanceId(id:string): string {
return id;
}
}`;

export const invalidGraphWithSubgraph = `import {
Graph,
ObjectGraph,
Provides,
} from 'src';
import Subgraph from './subgraph';
@Graph({ subgraphs: [Subgraph] })
export default class SimpleGraphWithSubgraph extends ObjectGraph {
@Provides()
someClass(wrongDep:string): string {
return wrongDep;
}
}`;
24 changes: 24 additions & 0 deletions eslint/undefinedDependencies/ruleConfiguration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ESLintUtils } from '@typescript-eslint/utils';
import { RuleModule } from '@typescript-eslint/utils/ts-eslint';
import { create } from './createFunction';

const createRule = ESLintUtils.RuleCreator(
(name) => `https://example.com/rule/${name}`,
);

export const undefinedDependencies: RuleModule<'undefinedDependency'> = createRule({
create,
name: 'undefined-dependency',
meta: {
docs: {
description: 'The dependency must be defined',
recommended: 'strict',
},
messages: {
undefinedDependency: 'Dependency {{ dependencyName }} is undefined',
},
schema: [],
type: 'problem',
},
defaultOptions: [] as [],
});
15 changes: 15 additions & 0 deletions eslint/undefinedDependencies/testUtils/subgraph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { uniqueId } from 'lodash';
import { Graph, ObjectGraph, Provides } from 'src';

@Graph()
export default class Subgraph extends ObjectGraph {
@Provides()
unusedDependency(): string {
throw Error('This dependency should not have been resolved since it is not required by anyone.');
}

@Provides()
instanceId(): string {
return uniqueId('graph');
}
}
Loading

0 comments on commit c184335

Please sign in to comment.