Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(packages/eslint-plugin-sui): Add rule to check relative imports #1760

Merged
merged 8 commits into from
May 10, 2024
1 change: 1 addition & 0 deletions packages/eslint-plugin-sui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"update:eslint-docs": "npx eslint-doc-generator"
},
"dependencies": {
"fast-glob": "3.3.2",
"requireindex": "1.2.0",
"string-dedent": "3.0.1"
},
Expand Down
4 changes: 3 additions & 1 deletion packages/eslint-plugin-sui/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const FactoryPattern = require('./rules/factory-pattern.js')
const SerializeDeserialize = require('./rules/serialize-deserialize.js')
const CommonJS = require('./rules/commonjs.js')
const Decorators = require('./rules/decorators.js')
const LayersArch = require('./rules/layers-architecture.js')

// ------------------------------------------------------------------------------
// Plugin Definition
Expand All @@ -13,6 +14,7 @@ module.exports = {
'factory-pattern': FactoryPattern,
'serialize-deserialize': SerializeDeserialize,
commonjs: CommonJS,
decorators: Decorators
decorators: Decorators,
layersArch: LayersArch
}
}
2 changes: 1 addition & 1 deletion packages/eslint-plugin-sui/src/rules/commonjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ module.exports = {
const isModule = node.callee?.object?.name === 'module' && node.callee?.property?.name === 'require'

const isRequireFormCreateRequire = node.parent?.parent?.body
?.filter(node => node.type === 'ImportDeclaration')
?.filter?.(node => node.type === 'ImportDeclaration')
?.some(
node =>
node.source?.value === 'module' && node.specifiers?.some(spec => spec.imported?.name === 'createRequire')
Expand Down
45 changes: 45 additions & 0 deletions packages/eslint-plugin-sui/src/rules/layers-architecture.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* @fileoverview Make sure to avoid direct file imports from other packages within your monorepo.
* */
'use strict'

const dedent = require('string-dedent')
const {Monorepo} = require('../utils/monorepo.js')

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'Make sure to avoid direct file imports from other packages within your monorepo',
recommended: true,
url: 'https://github.mpi-internal.com/scmspain/es-td-agreements/blob/master/30-Frontend/00-agreements'
},
fixable: null,
schema: [],
messages: {
forbiddenRelativeImports: dedent`
When using a package from your monorepo, import it directly instead of using a relative path.
`
}
},
create: function (context) {
const monorepo = Monorepo.create(context.cwd)

return {
ImportDeclaration(node) {
const isForbidden = monorepo.isPackage(context.filename, node.source.value)

isForbidden &&
context.report({
node,
messageId: 'forbiddenRelativeImports'
})
}
}
}
}
54 changes: 54 additions & 0 deletions packages/eslint-plugin-sui/src/utils/monorepo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const path = require('node:path')
const fg = require('fast-glob')

let instance

class MonoRepo {
static create(root) {
if (instance) return instance

instance = new MonoRepo(root)
return instance
}

constructor(root) {
const rootPackageJSON = require(path.join(root, 'package.json'))
const innerPatternPackagesJSON = rootPackageJSON.workspaces?.map(workspace => path.join(workspace, 'package.json'))
this._packages = innerPatternPackagesJSON ? fg.sync(innerPatternPackagesJSON, {deep: 3}) : []
this._root = root
}

get packages() {
return this._packages
}

get root() {
return this._root
}

belongSamePackage(filePath, relativeImport) {
return (
path.normalize(filePath)?.replace(this.root, '')?.split('/')?.at(1) ===
path
.normalize(path.dirname(filePath) + '/' + relativeImport)
?.replace(this.root, '')
?.split('/')
?.at(1)
)
}

isPackage(filePath, relativeImport) {
if (!relativeImport.startsWith('../')) return false
if (this.belongSamePackage(filePath, relativeImport)) return false

const pkgName = path
.normalize(path.dirname(filePath) + '/' + relativeImport)
?.replace(this.root, '')
?.replace(/(lib|src)\/.*/, 'package.json')
?.replace('/', '')

return this.packages.includes(pkgName)
}
}

module.exports.Monorepo = MonoRepo
67 changes: 67 additions & 0 deletions packages/eslint-plugin-sui/test/server/layers-architecture.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import dedent from 'dedent'
import {RuleTester} from 'eslint'
import sinon from 'sinon'

import rule from '../../src/rules/layers-architecture.js'
import {Monorepo} from '../../src/utils/monorepo.js'

const resolvedBabelPresetSui = require.resolve('babel-preset-sui')
const parser = require.resolve('@babel/eslint-parser')

const CWD = '/Users/carlosvillu/Developer/frontend-mt--web-app'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the new /foo/bar? 😉


const ruleTester = new RuleTester({parser, parserOptions: {babelOptions: {configFile: resolvedBabelPresetSui}}})
describe('layersArch valid', function () {
beforeEach(function () {
this.getterPackagesStub = sinon.stub(Monorepo.prototype, 'packages').get(() => ['domain/package.json'])
this.getterRootStub = sinon.stub(Monorepo.prototype, 'root').get(() => CWD)
})
afterEach(function () {
this.getterPackagesStub.restore()
this.getterRootStub.restore()
})

ruleTester.run('layersArch', rule, {
valid: [
{
filename: CWD + '/components/animation/fadeOut/demo/context.js',
code: dedent`
import DomainBuilder from 'studio-utils/DomainBuilder'

class User {
static create() { return new User() }
}
`
},
{
filename: CWD + '/components/animation/fadeOut/demo/context.js',
code: dedent`
import { createRequire } from "module"

class User {
static create() { return new User() }
}
`
}
],
invalid: [
{
filename: CWD + '/components/animation/fadeOut/demo/context.js',
code: dedent`
import {Coches as Domain} from '../../../../domain/lib/index.js'

class Model {
constructor() { this.name = 'John Doe' }
}
`,
errors: [
{
message: dedent`
When using a package from your monorepo, import it directly instead of using a relative path.
`
}
]
}
]
})
})
10 changes: 9 additions & 1 deletion packages/lint-repository-sui/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ const TypeScript = require('./rules/typescript.js')
const Structure = require('./rules/structure.js')
const SuiToolsVersion = require('./rules/sui-tools-version.js')
const ADVToolsVersion = require('./rules/adv-tools-version.js')
const TSvsJS = require('./rules/ts-vs-js-files.js')
const Sass = require('./rules/sass-files.js')
const Spark = require('./rules/spark-adoption.js')
const ComponentsLocation = require('./rules/components-location.js')

// ------------------------------------------------------------------------------
// Plugin Definition
Expand All @@ -23,6 +27,10 @@ module.exports = {
typescript: TypeScript,
structure: Structure,
'sui-tools-version': SuiToolsVersion,
'adv-tools-version': ADVToolsVersion
'adv-tools-version': ADVToolsVersion,
'ts-vs-js-files': TSvsJS,
'sass-files': Sass,
'spark-adoption': Spark,
'components-location': ComponentsLocation
}
}
3 changes: 0 additions & 3 deletions packages/lint-repository-sui/src/rules/adv-tools-version.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,13 @@ module.exports = {
badVersion: dedent`
Please be sure that your repository use the latest {{name}}. Version {{spectedVersion}}.
Your current version is {{version}}.
If you are not sure about how do it, please contact with Platform Web.
`,
missingDependency: dedent`
Your project doesnt have installed {{name}}.
Please install at least the version {{spectedVersion}}.
If you are not sure about how do it, please contact with Platform Web.
`,
missingPackageLock: dedent`
To calculate the ADV Tool version first we need to have a package-lock.json in the root
If you are not sure about how do it, please contact with Platform Web.
`
}
},
Expand Down
39 changes: 39 additions & 0 deletions packages/lint-repository-sui/src/rules/components-location.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const dedent = require('string-dedent')

const handler = (context, matches) => {
const badComponents = matches
.filter(match => match.path.match(/src\/pages\/.*/))
.filter(match => !match.path.match(/src\/pages\/\w+\/index\.(j|t)s(x)?/))
.filter(match => match.raw.match(/(?<tag><\w+\s*.*>)|(?<fragment><>)|(?<react>react)/)).length

context.report({
messageId: 'number',
data: {number: badComponents}
})
return context.monitoring(badComponents)
}

module.exports = {
meta: {
type: 'problem',
docs: {
description: 'This metric reports how many component live outside of our Studios.',
recommended: true,
url: null
},
fixable: null,
schema: [],
messages: {
number: dedent`
Currently, your project has {{number}} component living outside of your SUI-Studio.
Try to move all those component to your \`packages/ui/components\` folder.
`
}
},
create: function (context) {
return {
'app/src/pages/**/*.(j|t)s(x)?': handler.bind(undefined, context),
'src/**/*.(j|t)s(x)?': handler.bind(undefined, context)
}
}
}
3 changes: 0 additions & 3 deletions packages/lint-repository-sui/src/rules/cypress-version.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,13 @@ module.exports = {
badCypressVersion: dedent`
Please be sure that your repository use the latest Cypress Version ${CYPRESS_VERSION}.
Your current version is {{version}}.
If you are not sure about how do it, please contact with Platform Web.
`,
missingCypressDependencie: dedent`
Your project doesnt have installed Cypress.
Please install at least the version ${CYPRESS_VERSION}.
If you are not sure about how do it, please contact with Platform Web.
`,
missingPackageLock: dedent`
To calculate the cypress version first we need to have a package-lock.json in the root
If you are not sure about how do it, please contact with Platform Web.
`
}
},
Expand Down
3 changes: 0 additions & 3 deletions packages/lint-repository-sui/src/rules/github-action.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,12 @@ module.exports = {
messages: {
missingGithubFolder: dedent`
Every project needs to have a .github/worflows folder to be able to run CI/CD in GHA.
If you are not sure about how do it, please contact with Platform Web.
`,
missingMainWorkflow: dedent`
Every project needs to have a workflow to run on master.
If you are not sure about how do it, please contact with Platform Web.
`,
missingPRWorkflow: dedent`
Every project needs to have a workflow to run on every PR.
If you are not sure about how do it, please contact with Platform Web.
`
}
},
Expand Down
3 changes: 0 additions & 3 deletions packages/lint-repository-sui/src/rules/node-version.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,13 @@ module.exports = {
moreThanOneNVMRC: dedent`
Your project has more than one .nvmrc file. That can be dangerous.
Please, use onle ONE in the root of your project.
If you are not sure about how do it, please contact with Platform Web.
`,
badNodeVersion: dedent`
Your current Node version is {{version}}.
Please be sure that your repository use the latest Node Version ${NODE_VERSION}.
If you are not sure about how do it, please contact with Platform Web.
`,
noNMVRCFile: dedent`
Every project have to have a .npmrc file to define the node versión.
If you are not sure about how do it, please contact with Platform Web.
`
}
},
Expand Down
1 change: 0 additions & 1 deletion packages/lint-repository-sui/src/rules/package-lock.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ module.exports = {
messages: {
missingPackageLock: dedent`
Every project needs to have a package-lock.json file to be used in CI/CD.
If you are not sure about how do it, please contact with Platform Web.
`
}
},
Expand Down
3 changes: 0 additions & 3 deletions packages/lint-repository-sui/src/rules/react-version.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,13 @@ module.exports = {
badReactVersion: dedent`
Please be sure that your repository use the latest React Version ${REACT_VERSION}.
Your current version is {{version}}.
If you are not sure about how do it, please contact with Platform Web.
`,
missingReactDependencie: dedent`
Your project doesnt have installed React.
Please install at least the version ${REACT_VERSION}.
If you are not sure about how do it, please contact with Platform Web.
`,
missingPackageLock: dedent`
To calculate the react version first we need to have a package-lock.json in the root
If you are not sure about how do it, please contact with Platform Web.
`
}
},
Expand Down
39 changes: 39 additions & 0 deletions packages/lint-repository-sui/src/rules/sass-files.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const dedent = require('string-dedent')

module.exports = {
meta: {
type: 'problem',
docs: {
description: 'This metric reports the number of sass files in your repository',
recommended: true,
url: null
},
fixable: null,
schema: [],
messages: {
percentage: dedent`
Currently, your project has {{number}} sass files.
We should remove as many sass files as we can
`
}
},
create: function (context) {
return {
'**/*.scss': matches => {
context.report({
messageId: 'percentage',
data: {number: matches.length}
})
return context.monitoring(matches.length)
},

missmatch: key => {
context.report({
messageId: 'percentage',
data: {percentage: 0}
})
context.monitoring(0)
}
}
}
}
Loading