Skip to content

Commit

Permalink
feat(packages/eslint-plugin-sui): Add rule to check relative imports
Browse files Browse the repository at this point in the history
  • Loading branch information
carlosvillu committed May 7, 2024
1 parent e24f3e1 commit 07cd08b
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 1 deletion.
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
}
}
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'
})
}
}
}
}
41 changes: 41 additions & 0 deletions packages/eslint-plugin-sui/src/utils/monorepo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const path = require('node:path')
const fg = require('fast-glob')

let instance

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

return new MonoRepo(root)
}

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
}

isPackage(filePath, relativeImport) {
if (!relativeImport.startsWith('../')) 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'

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.
`
}
]
}
]
})
})

0 comments on commit 07cd08b

Please sign in to comment.