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

Prefer relative import instead of alias if the import is a subpath #160

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 0 additions & 27 deletions lib/helpers/create-fixer.js

This file was deleted.

74 changes: 41 additions & 33 deletions lib/rules/use-alias.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
const babel = require('@babel/core')
const path = require('path')
const fs = require('fs')
const { resolvePath } = require('babel-plugin-module-resolver')

const checkIgnoreDepth = require('../helpers/check-ignore-depth')
const findProjectRoot = require('../helpers/find-project-root')
const getProperties = require('../helpers/get-properties')
const createFixer = require('../helpers/create-fixer')

const values = Object.values || ((obj) => Object.keys(obj).map((e) => obj[e]))
const checkPath = (path, ext) => fs.existsSync(`${path}${ext}`) || fs.existsSync(`${path}/index${ext}`)

module.exports = {
Expand Down Expand Up @@ -72,7 +71,7 @@ module.exports = {
return plugin
}
})
alias = moduleResolver.options.alias || {}
alias = moduleResolver.options.alias
} catch (error) {
const message = 'Unable to find config for babel-plugin-module-resolver'
return {
Expand All @@ -85,11 +84,14 @@ module.exports = {
}
}

// Build array of alias paths.
// Resolve alias paths.
const cwd = projectRootAbsolutePath || process.cwd()
const aliasPaths = values(alias).map((a) => path.join(cwd, a))
const resolvedAlias = Object.fromEntries(
Object.entries(alias).map(([aliasName, aliasPath]) => [aliasName, path.join(cwd, aliasPath)]),
)

const hasError = (val) => {
const run = ({ node, source }) => {
const val = source.value
if (!val) return false // template literals will have undefined val

const { ignoreDepth, projectRoot, extensions, allowDepthMoreOrLessThanEquality } = options
Expand All @@ -99,58 +101,64 @@ module.exports = {

// Error if projectRoot option specified but cannot be resolved.
if (projectRoot && !projectRootAbsolutePath) {
return {
suggestFix: false,
return context.report({
node,
message: 'Invalid project root specified',
}
loc: source.loc,
})
}

const resolvedPath = path.resolve(filePath, val)
const containedAlias = Object.keys(resolvedAlias).find((name) => val.startsWith(name)) //new Regex(`^${alias}(\\/|$)`).test(val))
const resolvedPath = containedAlias
? val.replace(containedAlias, resolvedAlias[containedAlias])
: path.resolve(filePath, val)
const [insideAlias] =
Object.entries(resolvedAlias).find(([aliasName, aliasPath]) => resolvedPath.startsWith(aliasPath)) || []
const resolvedExt = path.extname(val) ? '' : '.js'
const isSubpath = resolvedPath.startsWith(filePath)

let pathExists = checkPath(resolvedPath, resolvedExt)

if (extensions && !pathExists) {
pathExists = extensions.filter((ext) => checkPath(resolvedPath, ext)).length
}

const isAliased = aliasPaths.some((aliasPath) => resolvedPath.includes(aliasPath))

const error = isAliased && pathExists && val.match(/\.\.\//) // matches, exists, and starts with ../,
return error && { suggestFix: true, message: 'Do not use relative path for aliased modules' }
}

const reportError = ({ node, source, error }) => {
context.report({
node,
message: error.message,
loc: source.loc,
fix: error.suggestFix && createFixer({ alias, filePath, cwd, node }),
})
if (insideAlias && pathExists && val.match(/\.\.\//)) {
// matches, exists, and starts with ../,
const newPath = resolvedPath.replace(resolvedAlias[insideAlias], '')
const replacement = path.join(insideAlias, newPath).replace(/\\/g, '/')
context.report({
node,
message: 'Do not use relative path for aliased modules',
loc: source.loc,
fix: (fixer) => fixer.replaceTextRange([source.range[0] + 1, source.range[1] - 1], replacement),
})
} else if (containedAlias && isSubpath) {
const replacement = resolvePath(val, filename, { alias })
context.report({
node,
message: 'Do not use aliased path for subpath import',
loc: source.loc,
fix: (fixer) => fixer.replaceTextRange([source.range[0] + 1, source.range[1] - 1], replacement),
})
}
}

return {
ImportDeclaration(node) {
const error = hasError(node.source.value)
if (error) {
reportError({ node, source: node.source, error })
}
run({ node, source: node.source })
},
CallExpression(node) {
const val = node.callee.name || node.callee.type
if (val === 'Import' || val === 'require') {
const error = hasError(node.arguments[0].value)
error && reportError({ node, source: node.arguments[0], error })
run({ node, source: node.arguments[0] })
}
},
ImportExpression(node) {
// dynamic import was erroneously using visitorKey for
// call expressions https://github.com/babel/babel/pull/10828
// adding ImportExpression for new versions of @babel/eslint-parser
const error = hasError(node.source.value)
if (error) {
reportError({ node, source: node.source, error })
}
run({ node, source: node.source })
},
}
},
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,8 @@
"regenerator-runtime": "^0.13.3",
"rimraf": "^3.0.0"
},
"license": "MIT"
"license": "MIT",
"dependencies": {
"babel-plugin-module-resolver": "^4.1.0"
}
}
11 changes: 0 additions & 11 deletions tests/helpers/create-fixer.test.js

This file was deleted.

54 changes: 40 additions & 14 deletions tests/lib/rules/use-alias.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,17 @@ describe('with babel config', () => {

ruleTester.run('module-resolver', rule, {
valid: [
"require('actions/api')",
"require('reducers/api')",
"require('ClientMain/api')",
"const { api } = require('actions/api')",
"const { api } = require('reducers/api')",
"import('actions/api')",
"import('reducers/api')",
{ code: "require('actions/api')", filename: `${projectRoot}/src/index.js` },
{ code: "require('reducers/api')", filename: `${projectRoot}/src/index.js` },
{ code: "const { api } = require('actions/api')", filename: `${projectRoot}/src/index.js` },
{ code: "const { api } = require('reducers/api')", filename: `${projectRoot}/src/index.js` },
{ code: "import('actions/api')", filename: `${projectRoot}/src/index.js` },
{ code: "import('reducers/api')", filename: `${projectRoot}/src/index.js` },
'import(`${buildPath}/dist`)',
"import { api } from 'actions/api'",
"import { api } from 'reducers/api'",
"const { api } = dynamic(import('actions/api'))",
"const { api } = dynamic(import('reducers/api'))",
{ code: "import { api } from 'actions/api'", filename: `${projectRoot}/src/index.js` },
{ code: "import { api } from 'reducers/api'", filename: `${projectRoot}/src/index.js` },
{ code: "const { api } = dynamic(import('actions/api'))", filename: `${projectRoot}/src/index.js` },
{ code: "const { api } = dynamic(import('reducers/api'))", filename: `${projectRoot}/src/index.js` },
'const { server } = require(`${buildPath}/dist`)',
"const { api } = require('./actions/api')",
"const { api } = require('./reducers/api')",
Expand All @@ -101,12 +100,20 @@ describe('with babel config', () => {
],

invalid: [
createInvalid({ code: "require('../actions/api')", type: 'CallExpression', output: "require('actions/api')" }),
createInvalid({ code: "require('../reducers/api')", type: 'CallExpression', output: "require('reducers/api')" }),
createInvalid({
code: "require('../actions/api')",
type: 'CallExpression',
output: "require('actions/api')",
}),
createInvalid({
code: "require('../reducers/api')",
type: 'CallExpression',
output: "require('reducers/api')",
}),
createInvalid({
code: "import('../../actions/api')",
type: 'ImportExpression',
filename: `${projectRoot}/src/client/index.js`,
type: 'ImportExpression',
output: "import('actions/api')",
}),
createInvalid({
Expand All @@ -115,6 +122,25 @@ describe('with babel config', () => {
filename: `${projectRoot}/src/client/index.js`,
output: "import('reducers/api')",
}),
createInvalid({
code: "import('./../actions/api')",
type: 'ImportExpression',
output: "import('actions/api')",
}),
createInvalid({
code: "import 'actions/api'",
filename: `${projectRoot}/index.js`,
type: 'ImportDeclaration',
output: "import './actions/api'",
errorMessage: 'Do not use aliased path for subpath import',
}),
createInvalid({
code: "import 'actions/api'",
filename: `${projectRoot}/actions/index.js`,
type: 'ImportDeclaration',
output: "import './api'",
errorMessage: 'Do not use aliased path for subpath import',
}),
createInvalid({
code: "const { api } = dynamic(import('../actions/api'))",
type: 'ImportExpression',
Expand Down
57 changes: 55 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1533,6 +1533,17 @@ babel-plugin-jest-hoist@^26.6.2:
"@types/babel__core" "^7.0.0"
"@types/babel__traverse" "^7.0.6"

babel-plugin-module-resolver@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/babel-plugin-module-resolver/-/babel-plugin-module-resolver-4.1.0.tgz#22a4f32f7441727ec1fbf4967b863e1e3e9f33e2"
integrity sha512-MlX10UDheRr3lb3P0WcaIdtCSRlxdQsB1sBqL7W0raF070bGl1HQQq5K3T2vf2XAYie+ww+5AKC/WrkjRO2knA==
dependencies:
find-babel-config "^1.2.0"
glob "^7.1.6"
pkg-up "^3.1.0"
reselect "^4.0.0"
resolve "^1.13.1"

babel-preset-current-node-syntax@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b"
Expand Down Expand Up @@ -2632,13 +2643,28 @@ fill-range@^7.0.1:
dependencies:
to-regex-range "^5.0.1"

find-babel-config@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/find-babel-config/-/find-babel-config-1.2.0.tgz#a9b7b317eb5b9860cda9d54740a8c8337a2283a2"
integrity sha512-jB2CHJeqy6a820ssiqwrKMeyC6nNdmrcgkKWJWmpoxpE8RKciYJXCcXRq1h2AzCo5I5BJeN2tkGEO3hLTuePRA==
dependencies:
json5 "^0.5.1"
path-exists "^3.0.0"

find-up@^2.0.0, find-up@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c=
dependencies:
locate-path "^2.0.0"

find-up@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==
dependencies:
locate-path "^3.0.0"

find-up@^4.0.0, find-up@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
Expand Down Expand Up @@ -2798,7 +2824,7 @@ glob-parent@^5.0.0, glob-parent@~5.1.0:
dependencies:
is-glob "^4.0.1"

glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
Expand Down Expand Up @@ -3968,6 +3994,14 @@ locate-path@^2.0.0:
p-locate "^2.0.0"
path-exists "^3.0.0"

locate-path@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==
dependencies:
p-locate "^3.0.0"
path-exists "^3.0.0"

locate-path@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
Expand Down Expand Up @@ -4364,7 +4398,7 @@ p-limit@^1.1.0:
dependencies:
p-try "^1.0.0"

p-limit@^2.2.0:
p-limit@^2.0.0, p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
Expand All @@ -4378,6 +4412,13 @@ p-locate@^2.0.0:
dependencies:
p-limit "^1.1.0"

p-locate@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==
dependencies:
p-limit "^2.0.0"

p-locate@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
Expand Down Expand Up @@ -4524,6 +4565,13 @@ pkg-dir@^4.2.0:
dependencies:
find-up "^4.0.0"

pkg-up@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5"
integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==
dependencies:
find-up "^3.0.0"

please-upgrade-node@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942"
Expand Down Expand Up @@ -4817,6 +4865,11 @@ require-main-filename@^2.0.0:
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==

reselect@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7"
integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==

resolve-cwd@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
Expand Down