Skip to content

[FEAT] Support multiple tsconfigs #22

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

Closed
wants to merge 5 commits into from
Closed
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
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ npm install --save-dev eslint-plugin-import @typescript-eslint/parser eslint-imp

Add the following to your `.eslintrc` config:

```CJSON
```JSONC
{
"plugins": ["import"],
"rules": {
Expand All @@ -37,6 +37,29 @@ Add the following to your `.eslintrc` config:
// use <root>/path/to/folder/tsconfig.json
"typescript": {
"directory": "./path/to/folder"
},

// Multiple tsconfigs (Useful for monorepos)

// use a glob pattern
"typescript": {
"directory": "./packages/**/tsconfig.json"
},

// use an array
"typescript": {
"directory": [
"./packages/module-a/tsconfig.json",
"./packages/module-b/tsconfig.json"
]
},

// use an array of glob patterns
"typescript": {
"directory": [
"./packages/**/tsconfig.json",
"./other-packages/**/tsconfig.json"
]
}
}
}
Expand Down
113 changes: 84 additions & 29 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,22 @@ const path = require('path');
const resolve = require('resolve');
const tsconfigPaths = require('tsconfig-paths');
const debug = require('debug');
const globSync = require('glob').sync;
const isGlob = require('is-glob');

const log = debug('eslint-import-resolver-typescript');

const extensions = Object.keys(require.extensions).concat(
'.ts',
'.tsx',
'.d.ts',
);

/**
* @param {string} source the module to resolve; i.e './some-module'
* @param {string} file the importing file's full path; i.e. '/usr/local/bin/file.js'
*/
function resolveFile(source, file, config) {
function resolveFile(source, file, options = {}) {
log('looking for:', source);

// don't worry about core node modules
Expand All @@ -24,38 +32,16 @@ function resolveFile(source, file, config) {
};
}

let foundTsPath = null;
const extensions = Object.keys(require.extensions).concat(
'.ts',
'.tsx',
'.d.ts',
);

// setup tsconfig-paths
const searchStart = config.directory || process.cwd();
const configLoaderResult = tsconfigPaths.loadConfig(searchStart);
if (configLoaderResult.resultType === 'success') {
const matchPath = tsconfigPaths.createMatchPath(
configLoaderResult.absoluteBaseUrl,
configLoaderResult.paths,
);

// look for files based on setup tsconfig "paths"
foundTsPath = matchPath(source, undefined, undefined, extensions);

if (foundTsPath) {
log('matched ts path:', foundTsPath);
}
} else {
log('failed to init tsconfig-paths:', configLoaderResult.message);
// this can happen if the user has problems with their tsconfig
// or if it's valid, but they don't have baseUrl set
initMappers(options);
const mappedPath = getMappedPath(source, file);
if (mappedPath) {
log('matched ts path:', mappedPath);
}

// note that even if we match via tsconfig-paths, we still need to do a final resolve
// note that even if we map the path, we still need to do a final resolve
let foundNodePath;
try {
foundNodePath = resolve.sync(foundTsPath || source, {
foundNodePath = resolve.sync(mappedPath || source, {
extensions,
basedir: path.dirname(path.resolve(file)),
packageFilter,
Expand All @@ -79,13 +65,82 @@ function resolveFile(source, file, config) {
found: false,
};
}

function packageFilter(pkg) {
if (pkg['jsnext:main']) {
pkg['main'] = pkg['jsnext:main'];
}
return pkg;
}

/**
* @param {string} source the module to resolve; i.e './some-module'
* @param {string} file the importing file's full path; i.e. '/usr/local/bin/file.js'
* @returns The mapped path of the module or undefined
*/
function getMappedPath(source, file) {
const paths = mappers
.map(mapper => mapper(source, file))
.filter(path => !!path);

if (paths.length > 1) {
log('found multiple matching ts paths:', paths);
}

return paths[0];
}

let mappers;
function initMappers(options) {
if (mappers) {
return;
}

const isArrayOfStrings = array =>
Array.isArray(array) && array.every(o => typeof o === 'string');

const configPaths =
typeof options.directory === 'string'
? [options.directory]
: isArrayOfStrings(options.directory)
? options.directory
: [process.cwd()];

mappers = configPaths
// turn glob patterns into paths
.reduce(
(paths, path) => paths.concat(isGlob(path) ? globSync(path) : path),
[],
)

.map(path => tsconfigPaths.loadConfig(path))
.filter(configLoaderResult => {
const success = configLoaderResult.resultType === 'success';
if (!success) {
// this can happen if the user has problems with their tsconfig
// or if it's valid, but they don't have baseUrl set
log('failed to init tsconfig-paths:', configLoaderResult.message);
}
return success;
})
.map(configLoaderResult => {
const matchPath = tsconfigPaths.createMatchPath(
configLoaderResult.absoluteBaseUrl,
configLoaderResult.paths,
);

return (source, file) => {
// exclude files that are not part of the config base url
if (!file.includes(configLoaderResult.absoluteBaseUrl)) {
return undefined;
}

// look for files based on setup tsconfig "paths"
return matchPath(source, undefined, undefined, extensions);
};
});
}

module.exports = {
interfaceVersion: 2,
resolve: resolveFile,
Expand Down
66 changes: 37 additions & 29 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
"license": "ISC",
"dependencies": {
"debug": "^4.0.1",
"glob": "^7.1.4",
"is-glob": "^4.0.1",
"resolve": "^1.4.0",
"tsconfig-paths": "^3.6.0"
},
Expand All @@ -36,7 +38,7 @@
"typescript": "^3.1.1"
},
"scripts": {
"test": "eslint ./tests/withPaths/index.ts && eslint ./tests/withoutPaths/index.ts",
"test": "eslint ./tests/withPaths/index.ts && eslint ./tests/withoutPaths/index.ts && eslint ./tests/multipleTsconfigs/**/index.ts",
"check-format": "prettier --config prettier.config.js index.js -l",
"format": "prettier --config prettier.config.js index.js --write"
}
Expand Down
8 changes: 8 additions & 0 deletions tests/multipleTsconfigs/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const path = require('path');

const globPattern = './packages/**/tsconfig.json';

// in normal cases this is not needed because the __dirname would be the root
const absoluteGlobPath = path.join(__dirname, globPattern);

module.exports = require('../baseEslintConfig')(absoluteGlobPath);
Loading