Skip to content

Commit

Permalink
Fix an issue where module resolution didn't follow symlinks. (palanti…
Browse files Browse the repository at this point in the history
…r#4295)

* Fix an issue where module resolution didn't follow symlinks.

* Fix several lint issues.

* Fix some style issues.
  • Loading branch information
iclanton authored and Josh Goldberg committed Nov 19, 2018
1 parent 6a4edd6 commit 1775e4a
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 24 deletions.
31 changes: 15 additions & 16 deletions src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,11 @@ import * as yaml from "js-yaml";
import { Minimatch } from "minimatch";
import * as os from "os";
import * as path from "path";
import * as resolve from "resolve";
import { FatalError, showWarningOnce } from "./error";

import { IOptions, RuleSeverity } from "./language/rule/rule";
import { findRule } from "./ruleLoader";
import { arrayify, hasOwnProperty, stripComments } from "./utils";
import { arrayify, hasOwnProperty, stripComments, tryResolvePackage } from "./utils";

export interface IConfigurationFile {
/**
Expand Down Expand Up @@ -309,17 +308,18 @@ function resolveConfigurationPath(filePath: string, relativeTo?: string) {

const basedir = relativeTo !== undefined ? relativeTo : process.cwd();
try {
return resolve.sync(filePath, { basedir });
} catch (err) {
try {
return require.resolve(filePath);
} catch (err) {
throw new Error(
`Invalid "extends" configuration value - could not require "${filePath}". ` +
"Review the Node lookup algorithm (https://nodejs.org/api/modules.html#modules_all_together) " +
"for the approximate method TSLint uses to find the referenced configuration file.",
);
let resolvedPackagePath: string | undefined = tryResolvePackage(filePath, basedir);
if (resolvedPackagePath === undefined) {
resolvedPackagePath = require.resolve(filePath);
}

return resolvedPackagePath;
} catch (err) {
throw new Error(
`Invalid "extends" configuration value - could not require "${filePath}". ` +
"Review the Node lookup algorithm (https://nodejs.org/api/modules.html#modules_all_together) " +
"for the approximate method TSLint uses to find the referenced configuration file.",
);
}
}

Expand Down Expand Up @@ -411,10 +411,9 @@ export function getRulesDirectories(
): string[] {
return arrayify(directories).map(dir => {
if (!useAsPath(dir)) {
try {
return path.dirname(resolve.sync(dir, { basedir: relativeTo }));
} catch (err) {
// swallow error and fallback to using directory as path
const resolvedPackagePath: string | undefined = tryResolvePackage(dir, relativeTo);
if (resolvedPackagePath !== undefined) {
return path.dirname(resolvedPackagePath);
}
}

Expand Down
15 changes: 7 additions & 8 deletions src/formatterLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@

import * as fs from "fs";
import * as path from "path";
import * as resolve from "resolve";
import { FormatterConstructor } from "./index";
import { camelize } from "./utils";
import { camelize, tryResolvePackage } from "./utils";

const CORE_FORMATTERS_DIRECTORY = path.resolve(__dirname, "formatters");

Expand Down Expand Up @@ -79,17 +78,17 @@ function loadFormatter(
}

function loadFormatterModule(name: string): FormatterConstructor | undefined {
let src: string;
let src: string | undefined;
try {
// first try to find a module in the dependencies of the currently linted project
src = resolve.sync(name, { basedir: process.cwd() });
} catch {
try {
src = tryResolvePackage(name, process.cwd());
if (src === undefined) {
// if there is no local module, try relative to the installation of TSLint (might be global)
src = require.resolve(name);
} catch {
return undefined;
}
} catch {
return undefined;
}

return (require(src) as { Formatter: FormatterConstructor }).Formatter;
}
31 changes: 31 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
* limitations under the License.
*/

import * as fs from "fs";
import * as resolve from "resolve";

/**
* Enforces the invariant that the input is an array.
*/
Expand Down Expand Up @@ -261,3 +264,31 @@ export function isKebabCased(name: string): boolean {
export function isSnakeCased(name: string): boolean {
return isSeparatorCased(name, "-");
}

/**
* Tries to resolve a package by name, optionally relative to a file path. If the
* file path is under a symlink, it tries to resolve the package under both the real path and under
* the symlink path.
*/
export function tryResolvePackage(packageName: string, relativeTo?: string): string | undefined {
const realRelativeToPath: string | undefined =
relativeTo !== undefined ? fs.realpathSync(relativeTo) : undefined;

let resolvedPath: string | undefined = tryResolveSync(packageName, realRelativeToPath);
if (resolvedPath === undefined) {
resolvedPath = tryResolveSync(packageName, relativeTo);
}

return resolvedPath;
}

/**
* Calls `resolve.sync` and if it fails, it returns `undefined`
*/
function tryResolveSync(packageName: string, relativeTo?: string): string | undefined {
try {
return resolve.sync(packageName, { basedir: relativeTo });
} catch {
return undefined;
}
}

0 comments on commit 1775e4a

Please sign in to comment.