diff --git a/types/eslint/eslint-tests.ts b/types/eslint/eslint-tests.ts new file mode 100644 index 0000000000..376cdf0a92 --- /dev/null +++ b/types/eslint/eslint-tests.ts @@ -0,0 +1,534 @@ +import { Comment } from 'estree'; +import { AST, SourceCode, Rule, Linter, CLIEngine, RuleTester, Scope } from 'eslint'; + +const SOURCE = `var foo = bar;`; + +const AST: AST.Program = { + type: 'Program', + sourceType: 'module', + body: [], + comments: [], + tokens: [], + loc: { + start: { line: 0, column: 0 }, + end: { line: 0, column: 0 } + }, + range: [0, 0], +}; + +const TOKEN: AST.Token = { + type: 'Identifier', + value: 'foo', + loc: { + start: { line: 0, column: 0 }, + end: { line: 0, column: 3 } + }, + range: [0, 3] +}; + +const COMMENT: Comment = { + type: 'Block', + value: 'foo', + loc: { + start: { line: 0, column: 0 }, + end: { line: 0, column: 0 } + }, + range: [0, 0], +}; + +//#region SourceCode + +let sourceCode = new SourceCode(SOURCE, AST); + +SourceCode.splitLines(SOURCE); + +sourceCode.getText(); +sourceCode.getText(AST); +sourceCode.getText(AST, 0); +sourceCode.getText(AST, 0, 0); + +sourceCode.getLines(); + +sourceCode.getAllComments(); + +sourceCode.getComments(AST).leading; +sourceCode.getComments(AST).trailing; + +sourceCode.getJSDocComment(AST); + +sourceCode.getNodeByRangeIndex(0); + +sourceCode.getNodeByRangeIndex(0); + +sourceCode.isSpaceBetweenTokens(TOKEN, TOKEN); + +sourceCode.getLocFromIndex(0); + +sourceCode.getIndexFromLoc({ line: 0, column: 0 }); + +sourceCode.getTokenByRangeStart(0); +sourceCode.getTokenByRangeStart(0, { includeComments: true }); + +sourceCode.getFirstToken(AST); +sourceCode.getFirstToken(AST, 0); +sourceCode.getFirstToken(AST, { skip: 0 }); +sourceCode.getFirstToken(AST, t => t.type === 'Identifier'); +sourceCode.getFirstToken(AST, { filter: t => t.type === 'Identifier' }); +sourceCode.getFirstToken(AST, { skip: 0, filter: t => t.type === 'Identifier' }); +sourceCode.getFirstToken(AST, { includeComments: true }); +sourceCode.getFirstToken(AST, { includeComments: true, skip: 0 }); +sourceCode.getFirstToken(AST, { includeComments: true, skip: 0, filter: t => t.type === 'Identifier' }); + +sourceCode.getFirstTokens(AST); +sourceCode.getFirstTokens(AST, 0); +sourceCode.getFirstTokens(AST, { count: 0 }); +sourceCode.getFirstTokens(AST, t => t.type === 'Identifier'); +sourceCode.getFirstTokens(AST, { filter: t => t.type === 'Identifier' }); +sourceCode.getFirstTokens(AST, { count: 0, filter: t => t.type === 'Identifier' }); +sourceCode.getFirstTokens(AST, { includeComments: true }); +sourceCode.getFirstTokens(AST, { includeComments: true, count: 0 }); +sourceCode.getFirstTokens(AST, { includeComments: true, count: 0, filter: t => t.type === 'Identifier' }); + +sourceCode.getLastToken(AST); +sourceCode.getLastToken(AST, 0); +sourceCode.getLastToken(AST, { skip: 0 }); +sourceCode.getLastToken(AST, t => t.type === 'Identifier'); +sourceCode.getLastToken(AST, { filter: t => t.type === 'Identifier' }); +sourceCode.getLastToken(AST, { skip: 0, filter: t => t.type === 'Identifier' }); +sourceCode.getLastToken(AST, { includeComments: true }); +sourceCode.getLastToken(AST, { includeComments: true, skip: 0 }); +sourceCode.getLastToken(AST, { includeComments: true, skip: 0, filter: t => t.type === 'Identifier' }); + +sourceCode.getLastTokens(AST); +sourceCode.getLastTokens(AST, 0); +sourceCode.getLastTokens(AST, { count: 0 }); +sourceCode.getLastTokens(AST, t => t.type === 'Identifier'); +sourceCode.getLastTokens(AST, { filter: t => t.type === 'Identifier' }); +sourceCode.getLastTokens(AST, { count: 0, filter: t => t.type === 'Identifier' }); +sourceCode.getLastTokens(AST, { includeComments: true }); +sourceCode.getLastTokens(AST, { includeComments: true, count: 0 }); +sourceCode.getLastTokens(AST, { includeComments: true, count: 0, filter: t => t.type === 'Identifier' }); + +sourceCode.getTokenBefore(AST); +sourceCode.getTokenBefore(AST, 0); +sourceCode.getTokenBefore(AST, { skip: 0 }); +sourceCode.getTokenBefore(AST, t => t.type === 'Identifier'); +sourceCode.getTokenBefore(AST, { filter: t => t.type === 'Identifier' }); +sourceCode.getTokenBefore(AST, { skip: 0, filter: t => t.type === 'Identifier' }); +sourceCode.getTokenBefore(AST, { includeComments: true }); +sourceCode.getTokenBefore(AST, { includeComments: true, skip: 0 }); +sourceCode.getTokenBefore(AST, { includeComments: true, skip: 0, filter: t => t.type === 'Identifier' }); +sourceCode.getTokenBefore(TOKEN, 0); +sourceCode.getTokenBefore(COMMENT, 0); + +sourceCode.getTokensBefore(AST); +sourceCode.getTokensBefore(AST, 0); +sourceCode.getTokensBefore(AST, { count: 0 }); +sourceCode.getTokensBefore(AST, t => t.type === 'Identifier'); +sourceCode.getTokensBefore(AST, { filter: t => t.type === 'Identifier' }); +sourceCode.getTokensBefore(AST, { count: 0, filter: t => t.type === 'Identifier' }); +sourceCode.getTokensBefore(AST, { includeComments: true }); +sourceCode.getTokensBefore(AST, { includeComments: true, count: 0 }); +sourceCode.getTokensBefore(AST, { includeComments: true, count: 0, filter: t => t.type === 'Identifier' }); +sourceCode.getTokensBefore(TOKEN, 0); +sourceCode.getTokensBefore(COMMENT, 0); + +sourceCode.getTokenAfter(AST); +sourceCode.getTokenAfter(AST, 0); +sourceCode.getTokenAfter(AST, { skip: 0 }); +sourceCode.getTokenAfter(AST, t => t.type === 'Identifier'); +sourceCode.getTokenAfter(AST, { filter: t => t.type === 'Identifier' }); +sourceCode.getTokenAfter(AST, { skip: 0, filter: t => t.type === 'Identifier' }); +sourceCode.getTokenAfter(AST, { includeComments: true }); +sourceCode.getTokenAfter(AST, { includeComments: true, skip: 0 }); +sourceCode.getTokenAfter(AST, { includeComments: true, skip: 0, filter: t => t.type === 'Identifier' }); +sourceCode.getTokenAfter(TOKEN, 0); +sourceCode.getTokenAfter(COMMENT, 0); + +sourceCode.getTokensAfter(AST); +sourceCode.getTokensAfter(AST, 0); +sourceCode.getTokensAfter(AST, { count: 0 }); +sourceCode.getTokensAfter(AST, t => t.type === 'Identifier'); +sourceCode.getTokensAfter(AST, { filter: t => t.type === 'Identifier' }); +sourceCode.getTokensAfter(AST, { count: 0, filter: t => t.type === 'Identifier' }); +sourceCode.getTokensAfter(AST, { includeComments: true }); +sourceCode.getTokensAfter(AST, { includeComments: true, count: 0 }); +sourceCode.getTokensAfter(AST, { includeComments: true, count: 0, filter: t => t.type === 'Identifier' }); +sourceCode.getTokensAfter(TOKEN, 0); +sourceCode.getTokensAfter(COMMENT, 0); + +sourceCode.getFirstTokenBetween(AST, AST); +sourceCode.getFirstTokenBetween(AST, AST, 0); +sourceCode.getFirstTokenBetween(AST, AST, { skip: 0 }); +sourceCode.getFirstTokenBetween(AST, AST, t => t.type === 'Identifier'); +sourceCode.getFirstTokenBetween(AST, AST, { filter: t => t.type === 'Identifier' }); +sourceCode.getFirstTokenBetween(AST, AST, { skip: 0, filter: t => t.type === 'Identifier' }); +sourceCode.getFirstTokenBetween(AST, AST, { includeComments: true }); +sourceCode.getFirstTokenBetween(AST, AST, { includeComments: true, skip: 0 }); +sourceCode.getFirstTokenBetween(AST, AST, { includeComments: true, skip: 0, filter: t => t.type === 'Identifier' }); + +sourceCode.getFirstTokensBetween(AST, AST); +sourceCode.getFirstTokensBetween(AST, AST, 0); +sourceCode.getFirstTokensBetween(AST, AST, { count: 0 }); +sourceCode.getFirstTokensBetween(AST, AST, t => t.type === 'Identifier'); +sourceCode.getFirstTokensBetween(AST, AST, { filter: t => t.type === 'Identifier' }); +sourceCode.getFirstTokensBetween(AST, AST, { count: 0, filter: t => t.type === 'Identifier' }); +sourceCode.getFirstTokensBetween(AST, AST, { includeComments: true }); +sourceCode.getFirstTokensBetween(AST, AST, { includeComments: true, count: 0 }); +sourceCode.getFirstTokensBetween(AST, AST, { includeComments: true, count: 0, filter: t => t.type === 'Identifier' }); + +sourceCode.getLastTokenBetween(AST, AST); +sourceCode.getLastTokenBetween(AST, AST, 0); +sourceCode.getLastTokenBetween(AST, AST, { skip: 0 }); +sourceCode.getLastTokenBetween(AST, AST, t => t.type === 'Identifier'); +sourceCode.getLastTokenBetween(AST, AST, { filter: t => t.type === 'Identifier' }); +sourceCode.getLastTokenBetween(AST, AST, { skip: 0, filter: t => t.type === 'Identifier' }); +sourceCode.getLastTokenBetween(AST, AST, { includeComments: true }); +sourceCode.getLastTokenBetween(AST, AST, { includeComments: true, skip: 0 }); +sourceCode.getLastTokenBetween(AST, AST, { includeComments: true, skip: 0, filter: t => t.type === 'Identifier' }); + +sourceCode.getLastTokensBetween(AST, AST); +sourceCode.getLastTokensBetween(AST, AST, 0); +sourceCode.getLastTokensBetween(AST, AST, { count: 0 }); +sourceCode.getLastTokensBetween(AST, AST, t => t.type === 'Identifier'); +sourceCode.getLastTokensBetween(AST, AST, { filter: t => t.type === 'Identifier' }); +sourceCode.getLastTokensBetween(AST, AST, { count: 0, filter: t => t.type === 'Identifier' }); +sourceCode.getLastTokensBetween(AST, AST, { includeComments: true }); +sourceCode.getLastTokensBetween(AST, AST, { includeComments: true, count: 0 }); +sourceCode.getLastTokensBetween(AST, AST, { includeComments: true, count: 0, filter: t => t.type === 'Identifier' }); + +sourceCode.getTokensBetween(AST, AST); +sourceCode.getTokensBetween(AST, AST, 0); + +sourceCode.getTokens(AST); +sourceCode.getTokens(AST, 0); +sourceCode.getTokens(AST, 0, 0); +sourceCode.getTokens(AST, t => t.type === 'Identifier'); +sourceCode.getTokens(AST, { filter: t => t.type === 'Identifier' }); +sourceCode.getTokens(AST, { includeComments: true }); +sourceCode.getTokens(AST, { includeComments: true, filter: t => t.type === 'Identifier' }); + +sourceCode.commentsExistBetween(AST, AST); +sourceCode.commentsExistBetween(TOKEN, TOKEN); + +sourceCode.getCommentsBefore(AST); +sourceCode.getCommentsBefore(TOKEN); + +sourceCode.getCommentsAfter(AST); +sourceCode.getCommentsAfter(TOKEN); + +sourceCode.getCommentsInside(AST); + +//#endregion + +//#region Scope + +const scopeManager: Scope.ScopeManager = { + scopes: [], + globalScope: null, + acquire(node, inner) { + return scopeManager.scopes[0]; + }, + getDeclaredVariables() { + return []; + } +}; + +const scope = scopeManager.scopes[0]; + +const variable = scope.variables[0]; + +variable.name = 'foo'; + +variable.identifiers[0].type = 'Identifier'; + +variable.defs[0].name.type = 'Identifier'; +variable.defs[0].type; +variable.defs[0].node; +variable.defs[0].parent; + +const reference = scope.references[0]; + +reference.from = scope; +reference.identifier.type = 'Identifier'; +reference.resolved = variable; +reference.writeExpr = AST; +reference.init = true; + +reference.isRead(); +reference.isReadOnly(); +reference.isWrite(); +reference.isWriteOnly(); +reference.isReadWrite(); + +//#endregion + +//#region Rule + +let rule: Rule.RuleModule; + +rule = { create(context) { return {}; } }; +rule = { create(context) { return {}; }, meta: {} }; +rule = { create(context) { return {}; }, meta: { + docs: { + description: 'disallow the use of `console`', + category: 'Possible Errors', + recommended: true, + url: 'https://eslint.org/docs/rules/no-console', + } +}}; +rule = { create(context) { return {}; }, meta: { fixable: 'whitespace' }}; +rule = { create(context) { return {}; }, meta: { fixable: 'code' }}; +rule = { create(context) { return {}; }, meta: { schema: [{ enum: ['always', 'never'] }] }}; +rule = { create(context) { return {}; }, meta: { deprecated: true }}; + +rule = { + create(context) { + context.getAncestors(); + + context.getDeclaredVariables(AST); + + context.getFilename(); + + context.getSourceCode(); + + context.getScope(); + + context.markVariableAsUsed('foo'); + + context.report({ message: 'foo', node: AST }); + context.report({ message: 'foo', loc: { line: 0, column: 0 } }); + context.report({ message: 'foo', node: AST, data: { foo: 'bar' } }); + context.report({ message: 'foo', node: AST, fix: () => null }); + context.report({ message: 'foo', node: AST, fix: ruleFixer => ruleFixer.replaceText(AST, 'foo') }); + + context.report({ + message: 'foo', + node: AST, + fix: ruleFixer => { + ruleFixer.insertTextAfter(AST, 'foo'); + ruleFixer.insertTextAfter(TOKEN, 'foo'); + + ruleFixer.insertTextAfterRange([0, 0], 'foo'); + + ruleFixer.insertTextBefore(AST, 'foo'); + ruleFixer.insertTextBefore(TOKEN, 'foo'); + + ruleFixer.insertTextBeforeRange([0, 0], 'foo'); + + ruleFixer.remove(AST); + ruleFixer.remove(TOKEN); + + ruleFixer.removeRange([0, 0]); + + ruleFixer.replaceText(AST, 'foo'); + ruleFixer.replaceText(TOKEN, 'foo'); + + ruleFixer.replaceTextRange([0, 0], 'foo'); + + return null; + } + }); + + return { + onCodePathStart(codePath, node) {}, + onCodePathEnd(codePath, node) {}, + onCodePathSegmentStart(segment, node) {}, + onCodePathSegmentEnd(segment, node) {}, + onCodePathSegmentLoop(fromSegment, toSegment, node) {}, + 'Program:exit'() {}, + }; + }, +}; + +//#endregion + +//#region Linter + +const linter = new Linter(); + +linter.version; + +linter.verify(SOURCE, {}); +linter.verify(new SourceCode(SOURCE, AST), {}); + +linter.verify(SOURCE, {}, 'test.js'); +linter.verify(SOURCE, {}, {}); +linter.verify(SOURCE, {}, { filename: 'test.js' }); +linter.verify(SOURCE, {}, { allowInlineConfig: false }); +linter.verify(SOURCE, {}, { reportUnusedDisableDirectives: true }); +linter.verify(SOURCE, {}, { preprocess: input => input.split(' ') }); +linter.verify(SOURCE, {}, { postprocess: problemList => problemList[0] }); + +linter.verify(SOURCE, { parserOptions: { ecmaVersion: 6 } }, 'test.js'); +linter.verify(SOURCE, { parserOptions: { ecmaVersion: 6, ecmaFeatures: { globalReturn: true } } }, 'test.js'); +linter.verify(SOURCE, { parserOptions: { ecmaVersion: 6, ecmaFeatures: { experimentalObjectRestSpread: true } } }, 'test.js'); +linter.verify(SOURCE, { env: { node: true } }, 'test.js'); +linter.verify(SOURCE, { globals: { foo: true } }, 'test.js'); +linter.verify(SOURCE, { parser: 'custom-parser' }, 'test.js'); +linter.verify(SOURCE, { settings: { info: 'foo' } }, 'test.js'); + +linter.verify(SOURCE, { rules: {} }, 'test.js'); +linter.verify(SOURCE, { rules: { quotes: 2 } }, 'test.js'); +linter.verify(SOURCE, { rules: { quotes: [2, 'double'] } }, 'test.js'); +linter.verify(SOURCE, { rules: { 'no-unused-vars': [2, { vars: 'all' }] } }, 'test.js'); +linter.verify(SOURCE, { rules: { 'no-console': 1 } }, 'test.js'); +linter.verify(SOURCE, { rules: { 'no-console': 0 } }, 'test.js'); +linter.verify(SOURCE, { rules: { 'no-console': 'error' } }, 'test.js'); +linter.verify(SOURCE, { rules: { 'no-console': 'warn' } }, 'test.js'); +linter.verify(SOURCE, { rules: { 'no-console': 'off' } }, 'test.js'); + +const lintingResult = linter.verify(SOURCE, {}); + +for (const msg of lintingResult) { + msg.severity = 1; + msg.severity = 2; + + msg.ruleId = 'foo'; + + msg.fatal = true; + + msg.line = 0; + msg.endLine = 0; + msg.column = 0; + msg.endColumn = 0; + + msg.source = SOURCE; + + if (msg.fix) { + msg.fix.text = 'foo'; + msg.fix.range = [0, 0]; + } +} + +linter.verifyAndFix(SOURCE, {}); +linter.verifyAndFix(SOURCE, {}, 'test.js'); +linter.verifyAndFix(SOURCE, {}, { fix: false }); + +const fixResult = linter.verifyAndFix(SOURCE, {}); + +fixResult.fixed = true; +fixResult.output = 'foo'; + +for (const msg of fixResult.messages) { + msg.ruleId = 'foo'; +} + +sourceCode = linter.getSourceCode(); + +linter.defineRule('test', rule); + +linter.defineRules({ + foo: rule, + bar: rule, +}); + +linter.getRules(); + +linter.defineParser('custom-parser', { parse: (src, opts) => AST }); +linter.defineParser('custom-parser', { + parseForESLint(src, opts) { + return { + ast: AST, + visitorKeys: {}, + parserServices: {}, + scopeManager, + }; + } +}); + +//#endregion + +//#region CLIEngine + +let cli: CLIEngine; + +cli = new CLIEngine({ allowInlineConfig: false }); +cli = new CLIEngine({ baseConfig: false }); +cli = new CLIEngine({ cache: true }); +cli = new CLIEngine({ cacheFile: 'foo' }); +cli = new CLIEngine({ configFile: 'foo' }); +cli = new CLIEngine({ cwd: 'foo' }); +cli = new CLIEngine({ envs: ['browser'] }); +cli = new CLIEngine({ extensions: ['js'] }); +cli = new CLIEngine({ fix: true }); +cli = new CLIEngine({ globals: ['foo'] }); +cli = new CLIEngine({ ignore: true }); +cli = new CLIEngine({ ignorePath: 'foo' }); +cli = new CLIEngine({ ignorePattern: 'foo' }); +cli = new CLIEngine({ useEslintrc: false }); +cli = new CLIEngine({ parserOptions: {} }); +cli = new CLIEngine({ plugins: ['foo'] }); +cli = new CLIEngine({ rules: { 'test/example-rule': 1 } }); +cli = new CLIEngine({ rulePaths: ['foo'] }); +cli = new CLIEngine({ reportUnusedDisableDirectives: true }); + +let cliReport = cli.executeOnFiles(['myfile.js', 'lib/']); + +cliReport = cli.executeOnText(SOURCE, 'foo'); + +cli.resolveFileGlobPatterns(['**/*']); + +cli.getConfigForFile('./config.json'); + +cli.addPlugin('my-fancy-plugin', {}); + +cli.isPathIgnored('./dist/index.js'); + +const formatter = cli.getFormatter('codeframe'); + +formatter(cliReport.results); + +CLIEngine.getErrorResults(cliReport.results); + +CLIEngine.outputFixes(cliReport); + +cliReport.errorCount = 0; +cliReport.warningCount = 0; +cliReport.fixableErrorCount = 0; +cliReport.fixableWarningCount = 0; + +for (const file of cliReport.results) { + file.filePath = 'foo.js'; + + file.errorCount = 0; + file.warningCount = 0; + file.fixableErrorCount = 0; + file.fixableWarningCount = 0; + + file.source = 'foo'; + file.output = 'foo'; + + for (const message of file.messages) { + message.ruleId = 'foo'; + } +} + +//#endregion + +//#region RuleTester + +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015 } }); + +ruleTester.run('my-rule', rule, { + valid: [ + { code: 'foo' }, + { code: 'foo', options: [{ allowFoo: true }] }, + { code: 'foo', filename: 'test.js' }, + { code: 'foo', parserOptions: {} }, + { code: 'foo', settings: { foo: true } }, + { code: 'foo', parser: 'foo' }, + { code: 'foo', globals: { foo: true } }, + ], + + invalid: [ + { code: 'foo', errors: 1 }, + { code: 'foo', errors: ['foo'] }, + { code: 'foo', errors: [{ message: 'foo' }] }, + { code: 'foo', errors: [{ message: 'foo', type: 'foo' }] }, + { code: 'foo', errors: [{ message: 'foo', data: { foo: true } }] }, + { code: 'foo', errors: [{ message: 'foo', line: 0 }] }, + ] +}); + +//#endregion diff --git a/types/eslint/index.d.ts b/types/eslint/index.d.ts new file mode 100644 index 0000000000..9e0e68af0b --- /dev/null +++ b/types/eslint/index.d.ts @@ -0,0 +1,573 @@ +// Type definitions for eslint 4.16 +// Project: https://eslint.org +// Definitions by: Pierre-Marie Dartus +// Jed Fox +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.2 + +import { JSONSchema4 } from 'json-schema'; +import * as ESTree from 'estree'; + +export namespace AST { + type TokenType = + | 'Boolean' + | 'Null' + | 'Identifier' + | 'Keyword' + | 'Punctuator' + | 'JSXIdentifier' + | 'JSXText' + | 'Numeric' + | 'String' + | 'RegularExpression'; + + interface Token { + type: TokenType; + value: string; + range: Range; + loc: SourceLocation; + } + + interface SourceLocation { + start: ESTree.Position; + end: ESTree.Position; + } + + type Range = [number, number]; + + interface Program extends ESTree.Program { + comments: ESTree.Comment[]; + tokens: Token[]; + loc: SourceLocation; + range: Range; + } +} + +export namespace Scope { + interface ScopeManager { + scopes: Scope[]; + globalScope: Scope | null; + + acquire(node: ESTree.Node, inner?: boolean): Scope | null; + + getDeclaredVariables(node: ESTree.Node): Variable[]; + } + + interface Scope { + type: 'block' | 'catch' | 'class' | 'for' | 'function' | 'function-expression-name' | 'global' | 'module' | 'switch' | 'with' | 'TDZ'; + isStrict: boolean; + upper: Scope | null; + childScopes: Scope[]; + variableScope: Scope; + block: ESTree.Node; + variables: Variable[]; + set: Map; + references: Reference[]; + through: Reference[]; + functionExpressionScope: boolean; + } + + interface Variable { + name: string; + identifiers: ESTree.Identifier[]; + references: Reference[]; + defs: Definition[]; + } + + interface Reference { + identifier: ESTree.Identifier; + from: Scope; + resolved: Variable | null; + writeExpr: ESTree.Node | null; + init: boolean; + + isWrite(): boolean; + + isRead(): boolean; + + isWriteOnly(): boolean; + + isReadOnly(): boolean; + + isReadWrite(): boolean; + } + + type DefinitionType = + | { type: 'CatchClause', node: ESTree.CatchClause, parent: null } + | { type: 'ClassName', node: ESTree.ClassDeclaration | ESTree.ClassExpression, parent: null } + | { type: 'FunctionName', node: ESTree.FunctionDeclaration | ESTree.FunctionExpression, parent: null } + | { type: 'ImplicitGlobalVariable', node: ESTree.Program, parent: null } + | { type: 'ImportBinding', node: ESTree.ImportSpecifier | ESTree.ImportDefaultSpecifier | ESTree.ImportNamespaceSpecifier, parent: ESTree.ImportDeclaration } + | { type: 'Parameter', node: ESTree.FunctionDeclaration | ESTree.FunctionExpression | ESTree.ArrowFunctionExpression, parent: null } + | { type: 'TDZ', node: any, parent: null } + | { type: 'Variable', node: ESTree.VariableDeclarator, parent: ESTree.VariableDeclaration }; + + type Definition = DefinitionType & { name: ESTree.Identifier }; +} + +//#region SourceCode + +export class SourceCode { + text: string; + ast: AST.Program; + lines: string[]; + hasBOM: boolean; + parserServices: SourceCode.ParserServices; + scopeManager: Scope.ScopeManager; + visitorKeys: SourceCode.VisitorKeys; + + constructor(text: string, ast: AST.Program); + constructor(config: SourceCode.Config); + + static splitLines(text: string): string[]; + + getText(node?: ESTree.Node, beforeCount?: number, afterCount?: number): string; + + getLines(): string[]; + + getAllComments(): ESTree.Comment[]; + + getComments(node: ESTree.Node): { leading: ESTree.Comment[], trailing: ESTree.Comment[] }; + + getJSDocComment(node: ESTree.Node): AST.Token | null; + + getNodeByRangeIndex(index: number): ESTree.Node | null; + + isSpaceBetweenTokens(first: AST.Token, second: AST.Token): boolean; + + getLocFromIndex(index: number): ESTree.SourceLocation; + + getIndexFromLoc(location: ESTree.Position): number; + + // Inherited methods from TokenStore + // --------------------------------- + + getTokenByRangeStart(offset: number, options?: { includeComments?: boolean }): AST.Token | null; + + getFirstToken(node: ESTree.Node, options?: SourceCode.CursorWithSkipOptions): AST.Token | null; + + getFirstTokens(node: ESTree.Node, options?: SourceCode.CursorWithCountOptions): AST.Token[]; + + getLastToken(node: ESTree.Node, options?: SourceCode.CursorWithSkipOptions): AST.Token | null; + + getLastTokens(node: ESTree.Node, options?: SourceCode.CursorWithCountOptions): AST.Token[]; + + getTokenBefore(node: ESTree.Node | AST.Token | ESTree.Comment, options?: SourceCode.CursorWithSkipOptions): AST.Token | null; + + getTokensBefore(node: ESTree.Node | AST.Token | ESTree.Comment, options?: SourceCode.CursorWithCountOptions): AST.Token[]; + + getTokenAfter(node: ESTree.Node | AST.Token | ESTree.Comment, options?: SourceCode.CursorWithSkipOptions): AST.Token | null; + + getTokensAfter(node: ESTree.Node | AST.Token | ESTree.Comment, options?: SourceCode.CursorWithCountOptions): AST.Token[]; + + getFirstTokenBetween( + left: ESTree.Node | AST.Token | ESTree.Comment, + right: ESTree.Node | AST.Token | ESTree.Comment, + options?: SourceCode.CursorWithSkipOptions + ): AST.Token | null; + + getFirstTokensBetween( + left: ESTree.Node | AST.Token | ESTree.Comment, + right: ESTree.Node | AST.Token | ESTree.Comment, + options?: SourceCode.CursorWithCountOptions + ): AST.Token[]; + + getLastTokenBetween( + left: ESTree.Node | AST.Token | ESTree.Comment, + right: ESTree.Node | AST.Token | ESTree.Comment, + options?: SourceCode.CursorWithSkipOptions + ): AST.Token | null; + + getLastTokensBetween( + left: ESTree.Node | AST.Token | ESTree.Comment, + right: ESTree.Node | AST.Token | ESTree.Comment, + options?: SourceCode.CursorWithCountOptions + ): AST.Token[]; + + getTokensBetween( + left: ESTree.Node | AST.Token | ESTree.Comment, + right: ESTree.Node | AST.Token | ESTree.Comment, + padding?: number | SourceCode.FilterPredicate | SourceCode.CursorWithCountOptions + ): AST.Token[]; + + getTokens(node: ESTree.Node, beforeCount?: number, afterCount?: number): AST.Token[]; + getTokens(node: ESTree.Node, options: SourceCode.FilterPredicate | SourceCode.CursorWithCountOptions): AST.Token[]; + + commentsExistBetween(left: ESTree.Node | AST.Token, right: ESTree.Node | AST.Token): boolean; + + getCommentsBefore(nodeOrToken: ESTree.Node | AST.Token): ESTree.Comment[]; + + getCommentsAfter(nodeOrToken: ESTree.Node | AST.Token): ESTree.Comment[]; + + getCommentsInside(node: ESTree.Node): ESTree.Comment[]; +} + +export namespace SourceCode { + interface Config { + text: string; + ast: AST.Program; + parserServices?: ParserServices; + scopeManager?: Scope.ScopeManager; + visitorKeys?: VisitorKeys; + } + + type ParserServices = any; + + interface VisitorKeys { + [nodeType: string]: string[]; + } + + type FilterPredicate = (tokenOrComment: AST.Token | ESTree.Comment) => boolean; + + type CursorWithSkipOptions = number | FilterPredicate | { + includeComments?: boolean; + filter?: FilterPredicate; + skip?: number; + }; + + type CursorWithCountOptions = number | FilterPredicate | { + includeComments?: boolean; + filter?: FilterPredicate; + count?: number; + }; +} + +//#endregion + +export namespace Rule { + interface RuleModule { + create(context: RuleContext): RuleListener; + meta?: RuleMetaData; + } + + interface RuleListener { + onCodePathStart?(codePath: CodePath, node: ESTree.Node): void; + + onCodePathEnd?(codePath: CodePath, node: ESTree.Node): void; + + onCodePathSegmentStart?(segment: CodePathSegment, node: ESTree.Node): void; + + onCodePathSegmentEnd?(segment: CodePathSegment, node: ESTree.Node): void; + + onCodePathSegmentLoop?(fromSegment: CodePathSegment, toSegment: CodePathSegment, node: ESTree.Node): void; + + [key: string]: + | ((codePath: CodePath, node: ESTree.Node) => void) + | ((segment: CodePathSegment, node: ESTree.Node) => void) + | ((fromSegment: CodePathSegment, toSegment: CodePathSegment, node: ESTree.Node) => void) + | ((node: ESTree.Node) => void) + | undefined; + } + + interface CodePath { + id: string; + initialSegment: CodePathSegment; + finalSegments: CodePathSegment[]; + returnedSegments: CodePathSegment[]; + thrownSegments: CodePathSegment[]; + currentSegments: CodePathSegment[]; + upper: CodePath | null; + childCodePaths: CodePath[]; + } + + interface CodePathSegment { + id: string; + nextSegments: CodePathSegment[]; + prevSegments: CodePathSegment[]; + reachable: boolean; + } + + interface RuleMetaData { + docs?: { + description?: string; + category?: string; + recommended?: boolean; + url?: string; + }; + messages?: { [messageId: string]: string }; + fixable?: 'code' | 'whitespace'; + schema?: JSONSchema4 | JSONSchema4[]; + deprecated?: boolean; + } + + interface RuleContext { + id: string; + options: any[]; + settings: { [name: string]: any }; + parserPath: string; + parserOptions: Linter.ParserOptions; + parserServices: SourceCode.ParserServices; + + getAncestors(): ESTree.Node[]; + + getDeclaredVariables(node: ESTree.Node): Scope.Variable[]; + + getFilename(): string; + + getScope(): Scope.Scope; + + getSourceCode(): SourceCode; + + markVariableAsUsed(name: string): boolean; + + report(descriptor: ReportDescriptor): void; + } + + type ReportDescriptor = ReportDescriptorMessage & ReportDescriptorLocation & ReportDescriptorOptions; + type ReportDescriptorMessage = { message: string } | { messageId: string }; + type ReportDescriptorLocation = { node: ESTree.Node } | { loc: AST.SourceLocation | { line: number, column: number } }; + interface ReportDescriptorOptions { + data?: { [key: string]: string }; + + fix?(fixer: RuleFixer): null | Fix | IterableIterator; + } + + interface RuleFixer { + insertTextAfter(nodeOrToken: ESTree.Node | AST.Token, text: string): Fix; + + insertTextAfterRange(range: AST.Range, text: string): Fix; + + insertTextBefore(nodeOrToken: ESTree.Node | AST.Token, text: string): Fix; + + insertTextBeforeRange(range: AST.Range, text: string): Fix; + + remove(nodeOrToken: ESTree.Node | AST.Token): Fix; + + removeRange(range: AST.Range): Fix; + + replaceText(nodeOrToken: ESTree.Node | AST.Token, text: string): Fix; + + replaceTextRange(range: AST.Range, text: string): Fix; + } + + interface Fix { + range: AST.Range; + text: string; + } +} + +//#region Linter + +export class Linter { + version: string; + + verify(code: SourceCode | string, config: Linter.Config, filename?: string): Linter.LintMessage[]; + verify(code: SourceCode | string, config: Linter.Config, options: Linter.LintOptions): Linter.LintMessage[]; + + verifyAndFix(code: string, config: Linter.Config, filename?: string): Linter.FixReport; + verifyAndFix(code: string, config: Linter.Config, options: Linter.FixOptions): Linter.FixReport; + + getSourceCode(): SourceCode; + + defineRule(name: string, rule: Rule.RuleModule): void; + + defineRules(rules: { [name: string]: Rule.RuleModule }): void; + + getRules(): Map; + + defineParser(name: string, parser: Linter.ParserModule): void; +} + +export namespace Linter { + type Severity = 0 | 1 | 2; + type RuleLevel = Severity | 'off' | 'warn' | 'error'; + + interface RuleLevelAndOptions extends Array { + 0: RuleLevel; + } + + interface Config { + rules?: { + [name: string]: RuleLevel | RuleLevelAndOptions + }; + parser?: string; + parserOptions?: ParserOptions; + settings?: { [name: string]: any }; + env?: { [name: string]: boolean }; + globals?: { [name: string]: boolean }; + } + + interface ParserOptions { + ecmaVersion?: 3 | 5 | 6 | 7 | 8 | 9 | 2015 | 2016 | 2017 | 2018; + sourceType?: 'script' | 'module'; + ecmaFeatures?: { + globalReturn?: boolean; + impliedStrict?: boolean; + jsx?: boolean; + experimentalObjectRestSpread?: boolean; + [key: string]: any; + }; + [key: string]: any; + } + + interface LintOptions { + filename?: string; + preprocess?: (code: string) => string[]; + postprocess?: (problemLists: LintMessage[][]) => LintMessage[]; + allowInlineConfig?: boolean; + reportUnusedDisableDirectives?: boolean; + } + + interface LintMessage { + column: number; + line: number; + endColumn?: number; + endLine?: number; + ruleId: string | null; + message: string; + nodeType: string; + fatal?: true; + severity: Severity; + fix?: Rule.Fix; + source: string | null; + } + + interface FixOptions extends LintOptions { + fix?: boolean; + } + + interface FixReport { + fixed: boolean; + output: string; + messages: LintMessage[]; + } + + type ParserModule = { + parse(text: string, options?: any): AST.Program; + } | { + parseForESLint(text: string, options?: any): ESLintParseResult; + }; + + interface ESLintParseResult { + ast: AST.Program; + parserServices?: SourceCode.ParserServices; + scopeManager?: Scope.ScopeManager; + visitorKeys?: SourceCode.VisitorKeys; + } +} + +//#endregion + +//#region CLIEngine + +export class CLIEngine { + version: string; + + constructor(options: CLIEngine.Options); + + executeOnFiles(patterns: string[]): CLIEngine.LintReport; + + resolveFileGlobPatterns(patterns: string[]): string[]; + + getConfigForFile(filePath: string): Linter.Config; + + executeOnText(text: string, filename?: string): CLIEngine.LintReport; + + addPlugin(name: string, pluginObject: any): void; + + isPathIgnored(filePath: string): boolean; + + getFormatter(format: string): CLIEngine.Formatter; + + getRules(): Map; + + static getErrorResults(results: CLIEngine.LintResult[]): CLIEngine.LintResult[]; + + static outputFixes(report: CLIEngine.LintReport): void; +} + +export namespace CLIEngine { + class Options { + allowInlineConfig?: boolean; + baseConfig?: boolean; + cache?: boolean; + cacheFile?: string; + cacheLocation?: string; + configFile?: string; + cwd?: string; + envs?: string[]; + extensions?: string[]; + fix?: boolean; + globals?: string[]; + ignore?: boolean; + ignorePath?: string; + ignorePattern?: string; + useEslintrc?: boolean; + parser?: string; + parserOptions?: Linter.ParserOptions; + plugins?: string[]; + rules?: { + [name: string]: Linter.RuleLevel | Linter.RuleLevelAndOptions; + }; + rulePaths?: string[]; + reportUnusedDisableDirectives?: boolean; + } + + interface LintResult { + filePath: string; + messages: Linter.LintMessage[]; + errorCount: number; + warningCount: number; + fixableErrorCount: number; + fixableWarningCount: number; + output?: string; + source?: string; + } + + interface LintReport { + results: LintResult[]; + errorCount: number; + warningCount: number; + fixableErrorCount: number; + fixableWarningCount: number; + } + + type Formatter = (results: LintResult[]) => string; +} + +//#endregion + +//#region RuleTester + +export class RuleTester { + constructor(config?: any); + + run( + name: string, + rule: Rule.RuleModule, + tests: { + valid?: RuleTester.ValidTestCase[]; + invalid?: RuleTester.InvalidTestCase[]; + }, + ): void; +} + +export namespace RuleTester { + interface ValidTestCase { + code: string; + options?: any; + filename?: string; + parserOptions?: Linter.ParserOptions; + settings?: { [name: string]: any }; + parser?: string; + globals?: { [name: string]: boolean }; + } + + interface InvalidTestCase extends ValidTestCase { + errors: number | Array; + output?: string | null; + } + + interface TestCaseError { + message?: string | RegExp; + messageId?: string; + type?: string; + data?: any; + line?: number; + column?: number; + endLine?: number; + endColumn?: number; + } +} + +//#endregion diff --git a/types/eslint/tsconfig.json b/types/eslint/tsconfig.json new file mode 100644 index 0000000000..4f53b4a06b --- /dev/null +++ b/types/eslint/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es6" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "baseUrl": "../", + "typeRoots": [ + "../" + ], + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true, + "esModuleInterop": true + }, + "files": [ + "index.d.ts", + "eslint-tests.ts" + ] +} diff --git a/types/eslint/tslint.json b/types/eslint/tslint.json new file mode 100644 index 0000000000..3db14f85ea --- /dev/null +++ b/types/eslint/tslint.json @@ -0,0 +1 @@ +{ "extends": "dtslint/dt.json" }