|
| 1 | +import { IOptions, IRule } from 'tslint'; |
| 2 | +import * as fs from 'fs'; |
| 3 | +import * as path from 'path'; |
| 4 | + |
| 5 | +export function convertRuleOptions(ruleConfiguration: Map<string, Partial<IOptions>>): IOptions[] { |
| 6 | + const output: IOptions[] = []; |
| 7 | + ruleConfiguration.forEach(({ ruleArguments, ruleSeverity }, ruleName) => { |
| 8 | + const options: IOptions = { |
| 9 | + disabledIntervals: [], // deprecated, so just provide an empty array. |
| 10 | + ruleArguments: ruleArguments !== null ? ruleArguments : [], |
| 11 | + ruleName, |
| 12 | + ruleSeverity: ruleSeverity !== null ? ruleSeverity : 'error', |
| 13 | + }; |
| 14 | + output.push(options); |
| 15 | + }); |
| 16 | + return output; |
| 17 | +} |
| 18 | + |
| 19 | +const cachedRules = new Map<string, any | 'not-found'>(); |
| 20 | + |
| 21 | +export function camelize(stringWithHyphens: string): string { |
| 22 | + return stringWithHyphens.replace(/-(.)/g, (_, nextLetter) => (nextLetter as string).toUpperCase()); |
| 23 | +} |
| 24 | + |
| 25 | +function transformName(name: string): string { |
| 26 | + // camelize strips out leading and trailing underscores and dashes, so make sure they aren't passed to camelize |
| 27 | + // the regex matches the groups (leading underscores and dashes)(other characters)(trailing underscores and dashes) |
| 28 | + const nameMatch = name.match(/^([-_]*)(.*?)([-_]*)$/); |
| 29 | + if (nameMatch === null) { |
| 30 | + return `${name}Rule`; |
| 31 | + } |
| 32 | + return `${nameMatch[1]}${camelize(nameMatch[2])}${nameMatch[3]}Rule`; |
| 33 | +} |
| 34 | + |
| 35 | +/** |
| 36 | + * @param directory - An absolute path to a directory of rules |
| 37 | + * @param ruleName - A name of a rule in filename format. ex) "someLintRule" |
| 38 | + */ |
| 39 | +function loadRule(directory: string, ruleName: string): any | 'not-found' { |
| 40 | + const fullPath = path.join(directory, ruleName); |
| 41 | + if (fs.existsSync(`${fullPath}.js`)) { |
| 42 | + const ruleModule = require(fullPath) as { Rule: any } | undefined; |
| 43 | + if (ruleModule !== undefined) { |
| 44 | + return ruleModule.Rule; |
| 45 | + } |
| 46 | + } |
| 47 | + return 'not-found'; |
| 48 | +} |
| 49 | + |
| 50 | +export function getRelativePath(directory?: string | null, relativeTo?: string) { |
| 51 | + if (directory !== null) { |
| 52 | + const basePath = relativeTo !== undefined ? relativeTo : process.cwd(); |
| 53 | + return path.resolve(basePath, directory); |
| 54 | + } |
| 55 | + return undefined; |
| 56 | +} |
| 57 | + |
| 58 | +export function arrayify<T>(arg?: T | T[]): T[] { |
| 59 | + if (Array.isArray(arg)) { |
| 60 | + return arg; |
| 61 | + } else if (arg !== null) { |
| 62 | + return [arg]; |
| 63 | + } else { |
| 64 | + return []; |
| 65 | + } |
| 66 | +} |
| 67 | + |
| 68 | +function loadCachedRule(directory: string, ruleName: string, isCustomPath?: boolean): any | undefined { |
| 69 | + // use cached value if available |
| 70 | + const fullPath = path.join(directory, ruleName); |
| 71 | + const cachedRule = cachedRules.get(fullPath); |
| 72 | + if (cachedRule !== undefined) { |
| 73 | + return cachedRule === 'not-found' ? undefined : cachedRule; |
| 74 | + } |
| 75 | + |
| 76 | + // get absolute path |
| 77 | + let absolutePath: string | undefined = directory; |
| 78 | + if (isCustomPath) { |
| 79 | + absolutePath = getRelativePath(directory); |
| 80 | + if (absolutePath !== undefined && !fs.existsSync(absolutePath)) { |
| 81 | + throw new Error(`Could not find custom rule directory: ${directory}`); |
| 82 | + } |
| 83 | + } |
| 84 | + |
| 85 | + const Rule = absolutePath === undefined ? 'not-found' : loadRule(absolutePath, ruleName); |
| 86 | + |
| 87 | + cachedRules.set(fullPath, Rule); |
| 88 | + return Rule === 'not-found' ? undefined : Rule; |
| 89 | +} |
| 90 | + |
| 91 | +export function find<T, U>(inputs: T[], getResult: (t: T) => U | undefined): U | undefined { |
| 92 | + for (const element of inputs) { |
| 93 | + const result = getResult(element); |
| 94 | + if (result !== undefined) { |
| 95 | + return result; |
| 96 | + } |
| 97 | + } |
| 98 | + return undefined; |
| 99 | +} |
| 100 | + |
| 101 | +function findRule(name: string, rulesDirectories?: string | string[]): any | undefined { |
| 102 | + const camelizedName = transformName(name); |
| 103 | + return find(arrayify(rulesDirectories), (dir) => loadCachedRule(dir, camelizedName, true)); |
| 104 | +} |
| 105 | + |
| 106 | +export function loadRules(ruleOptionsList: IOptions[], |
| 107 | + rulesDirectories?: string | string[], |
| 108 | + isJs = false): IRule[] { |
| 109 | + const rules: IRule[] = []; |
| 110 | + const notFoundRules: string[] = []; |
| 111 | + const notAllowedInJsRules: string[] = []; |
| 112 | + |
| 113 | + for (const ruleOptions of ruleOptionsList) { |
| 114 | + if (ruleOptions.ruleSeverity === 'off') { |
| 115 | + // Perf: don't bother finding the rule if it's disabled. |
| 116 | + continue; |
| 117 | + } |
| 118 | + |
| 119 | + const ruleName = ruleOptions.ruleName; |
| 120 | + const Rule = findRule(ruleName, rulesDirectories); |
| 121 | + if (Rule === undefined) { |
| 122 | + notFoundRules.push(ruleName); |
| 123 | + } else if (isJs && Rule.metadata !== undefined && Rule.metadata.typescriptOnly) { |
| 124 | + notAllowedInJsRules.push(ruleName); |
| 125 | + } else { |
| 126 | + const rule = new Rule(ruleOptions); |
| 127 | + if (rule.isEnabled()) { |
| 128 | + rules.push(rule); |
| 129 | + } |
| 130 | + } |
| 131 | + } |
| 132 | + return rules; |
| 133 | +} |
0 commit comments