[ES|QL] unskip autocomplete tests (#182273)

## Summary

A `.only` snuck into the autocomplete tests a few PRs back and this PR
fixes that... FOR GOOD :D
This commit is contained in:
Drew Tate 2024-05-02 08:37:50 -06:00 committed by GitHub
parent 15d2235a73
commit b694a894ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 146 additions and 113 deletions

View file

@ -16,12 +16,7 @@ module.exports = {
files: ['**/*.{ts,tsx}'], files: ['**/*.{ts,tsx}'],
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
plugins: [ plugins: ['@typescript-eslint', 'ban', 'import', 'eslint-comments'],
'@typescript-eslint',
'ban',
'import',
'eslint-comments'
],
env: { env: {
es6: true, es6: true,
@ -34,7 +29,7 @@ module.exports = {
sourceType: 'module', sourceType: 'module',
ecmaVersion: 2018, ecmaVersion: 2018,
ecmaFeatures: { ecmaFeatures: {
jsx: true jsx: true,
}, },
// NOTE: That is to avoid a known performance issue related with the `ts.Program` used by // NOTE: That is to avoid a known performance issue related with the `ts.Program` used by
// typescript eslint. As we are not using rules that need types information, we can safely // typescript eslint. As we are not using rules that need types information, we can safely
@ -43,7 +38,7 @@ module.exports = {
// https://github.com/typescript-eslint/typescript-eslint/issues/389 // https://github.com/typescript-eslint/typescript-eslint/issues/389
// https://github.com/typescript-eslint/typescript-eslint/issues/243 // https://github.com/typescript-eslint/typescript-eslint/issues/243
// https://github.com/typescript-eslint/typescript-eslint/pull/361 // https://github.com/typescript-eslint/typescript-eslint/pull/361
project: undefined project: undefined,
}, },
// NOTE: we can't override the extends option here to apply // NOTE: we can't override the extends option here to apply
@ -60,32 +55,38 @@ module.exports = {
// //
// Old recommended tslint rules // Old recommended tslint rules
'@typescript-eslint/adjacent-overload-signatures': 'error', '@typescript-eslint/adjacent-overload-signatures': 'error',
'@typescript-eslint/array-type': ['error', { default: 'array-simple', readonly: 'array-simple' }], '@typescript-eslint/array-type': [
'@typescript-eslint/ban-types': ['error', { 'error',
types: { { default: 'array-simple', readonly: 'array-simple' },
SFC: { ],
message: 'Use FC or FunctionComponent instead.', '@typescript-eslint/ban-types': [
fixWith: 'FC' 'error',
{
types: {
SFC: {
message: 'Use FC or FunctionComponent instead.',
fixWith: 'FC',
},
'React.SFC': {
message: 'Use FC or FunctionComponent instead.',
fixWith: 'React.FC',
},
StatelessComponent: {
message: 'Use FunctionComponent instead.',
fixWith: 'FunctionComponent',
},
'React.StatelessComponent': {
message: 'Use FunctionComponent instead.',
fixWith: 'React.FunctionComponent',
},
// used in the codebase in the wild
'{}': false,
object: false,
Function: false,
}, },
'React.SFC': { },
message: 'Use FC or FunctionComponent instead.', ],
fixWith: 'React.FC' camelcase: 'off',
},
StatelessComponent: {
message: 'Use FunctionComponent instead.',
fixWith: 'FunctionComponent'
},
'React.StatelessComponent': {
message: 'Use FunctionComponent instead.',
fixWith: 'React.FunctionComponent'
},
// used in the codebase in the wild
'{}': false,
'object': false,
'Function': false,
}
}],
'camelcase': 'off',
'@typescript-eslint/naming-convention': [ '@typescript-eslint/naming-convention': [
'error', 'error',
{ {
@ -93,8 +94,8 @@ module.exports = {
format: ['camelCase'], format: ['camelCase'],
filter: { filter: {
regex: allowedNameRegexp, regex: allowedNameRegexp,
match: false match: false,
} },
}, },
{ {
selector: 'variable', selector: 'variable',
@ -105,19 +106,16 @@ module.exports = {
], ],
filter: { filter: {
regex: allowedNameRegexp, regex: allowedNameRegexp,
match: false match: false,
} },
}, },
{ {
selector: 'parameter', selector: 'parameter',
format: [ format: ['camelCase', 'PascalCase'],
'camelCase',
'PascalCase',
],
filter: { filter: {
regex: allowedNameRegexp, regex: allowedNameRegexp,
match: false match: false,
} },
}, },
{ {
selector: 'memberLike', selector: 'memberLike',
@ -125,23 +123,23 @@ module.exports = {
'camelCase', 'camelCase',
'PascalCase', 'PascalCase',
'snake_case', // keys in elasticsearch requests / responses 'snake_case', // keys in elasticsearch requests / responses
'UPPER_CASE' 'UPPER_CASE',
], ],
filter: { filter: {
regex: allowedNameRegexp, regex: allowedNameRegexp,
match: false match: false,
} },
}, },
{ {
selector: 'function', selector: 'function',
format: [ format: [
'camelCase', 'camelCase',
'PascalCase' // React.FunctionComponent = 'PascalCase', // React.FunctionComponent =
], ],
filter: { filter: {
regex: allowedNameRegexp, regex: allowedNameRegexp,
match: false match: false,
} },
}, },
{ {
selector: 'typeLike', selector: 'typeLike',
@ -164,27 +162,31 @@ module.exports = {
'objectLiteralMethod', 'objectLiteralMethod',
'typeMethod', 'typeMethod',
'accessor', 'accessor',
'enumMember' 'enumMember',
], ],
format: null, format: null,
modifiers: ['requiresQuotes'] modifiers: ['requiresQuotes'],
} },
], ],
'@typescript-eslint/explicit-member-accessibility': ['error', '@typescript-eslint/explicit-member-accessibility': [
'error',
{ {
accessibility: 'off', accessibility: 'off',
overrides: { overrides: {
accessors: 'explicit', accessors: 'explicit',
constructors: 'no-public', constructors: 'no-public',
parameterProperties: 'explicit' parameterProperties: 'explicit',
} },
} },
], ],
'@typescript-eslint/prefer-function-type': 'error', '@typescript-eslint/prefer-function-type': 'error',
'@typescript-eslint/consistent-type-definitions': ['error', 'interface'], '@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
'@typescript-eslint/member-ordering': ['error', { '@typescript-eslint/member-ordering': [
'default': ['public-static-field', 'static-field', 'instance-field'] 'error',
}], {
default: ['public-static-field', 'static-field', 'instance-field'],
},
],
'@typescript-eslint/consistent-type-assertions': 'error', '@typescript-eslint/consistent-type-assertions': 'error',
'@typescript-eslint/no-empty-interface': 'error', '@typescript-eslint/no-empty-interface': 'error',
'@typescript-eslint/no-extra-non-null-assertion': 'error', '@typescript-eslint/no-extra-non-null-assertion': 'error',
@ -195,24 +197,26 @@ module.exports = {
'@typescript-eslint/no-undef': 'off', '@typescript-eslint/no-undef': 'off',
'no-undef': 'off', 'no-undef': 'off',
'@typescript-eslint/triple-slash-reference': ['error', { '@typescript-eslint/triple-slash-reference': [
path: 'never', 'error',
types: 'never', {
lib: 'never' path: 'never',
}], types: 'never',
lib: 'never',
},
],
'@typescript-eslint/no-var-requires': 'error', '@typescript-eslint/no-var-requires': 'error',
'@typescript-eslint/unified-signatures': 'error', '@typescript-eslint/unified-signatures': 'error',
'constructor-super': 'error', 'constructor-super': 'error',
'dot-notation': 'error', 'dot-notation': 'error',
'eqeqeq': ['error', 'always', {'null': 'ignore'}], eqeqeq: ['error', 'always', { null: 'ignore' }],
'guard-for-in': 'error', 'guard-for-in': 'error',
'import/order': ['error', { 'import/order': [
'groups': [ 'error',
['external', 'builtin'], {
'internal', groups: [['external', 'builtin'], 'internal', ['parent', 'sibling', 'index']],
['parent', 'sibling', 'index'], },
], ],
}],
'max-classes-per-file': ['error', 1], 'max-classes-per-file': ['error', 1],
'no-bitwise': 'error', 'no-bitwise': 'error',
'no-caller': 'error', 'no-caller': 'error',
@ -233,22 +237,27 @@ module.exports = {
'no-unused-labels': 'error', 'no-unused-labels': 'error',
'no-var': 'error', 'no-var': 'error',
'object-shorthand': 'error', 'object-shorthand': 'error',
'one-var': [ 'error', 'never' ], 'one-var': ['error', 'never'],
'prefer-const': 'error', 'prefer-const': 'error',
'prefer-rest-params': 'error', 'prefer-rest-params': 'error',
'radix': 'error', radix: 'error',
'spaced-comment': ["error", "always", { 'spaced-comment': [
"exceptions": ["/"] 'error',
}], 'always',
{
exceptions: ['/'],
},
],
'use-isnan': 'error', 'use-isnan': 'error',
// Old tslint yml override or defined rules // Old tslint yml override or defined rules
'ban/ban': [ 'ban/ban': [
2, 2,
{'name': ['describe', 'only'], 'message': 'No exclusive suites.'}, { name: ['describe', 'only'], message: 'No exclusive suites.' },
{'name': ['it', 'only'], 'message': 'No exclusive tests.'}, { name: ['it', 'only'], message: 'No exclusive tests.' },
{'name': ['test', 'only'], 'message': 'No exclusive tests.'}, { name: ['test', 'only'], message: 'No exclusive tests.' },
{ name: ['testSuggestions', 'only'], message: 'No exclusive tests.' },
{ name: ['testErrorsAndWarnings', 'only'], message: 'No exclusive tests.' },
], ],
'import/no-default-export': 'error', 'import/no-default-export': 'error',
@ -257,13 +266,13 @@ module.exports = {
'no-restricted-syntax': [ 'no-restricted-syntax': [
'error', 'error',
{ {
"selector": "TSEnumDeclaration[const=true]", selector: 'TSEnumDeclaration[const=true]',
"message": "Do not use `const` with enum declarations" message: 'Do not use `const` with enum declarations',
} },
] ],
}, },
eslintConfigPrettierRules eslintConfigPrettierRules
) ),
}, },
] ],
}; };

View file

@ -12,10 +12,9 @@ import { builtinFunctions } from '../definitions/builtin';
import { statsAggregationFunctionDefinitions } from '../definitions/aggs'; import { statsAggregationFunctionDefinitions } from '../definitions/aggs';
import { chronoLiterals, timeLiterals } from '../definitions/literals'; import { chronoLiterals, timeLiterals } from '../definitions/literals';
import { commandDefinitions } from '../definitions/commands'; import { commandDefinitions } from '../definitions/commands';
import { TRIGGER_SUGGESTION_COMMAND } from './factories'; import { getUnitDuration, TRIGGER_SUGGESTION_COMMAND } from './factories';
import { camelCase } from 'lodash'; import { camelCase } from 'lodash';
import { getAstAndSyntaxErrors } from '@kbn/esql-ast'; import { getAstAndSyntaxErrors } from '@kbn/esql-ast';
import { SuggestionRawDefinition } from './types';
import { groupingFunctionDefinitions } from '../definitions/grouping'; import { groupingFunctionDefinitions } from '../definitions/grouping';
const triggerCharacters = [',', '(', '=', ' ']; const triggerCharacters = [',', '(', '=', ' '];
@ -231,14 +230,14 @@ function getPolicyFields(policyName: string) {
describe('autocomplete', () => { describe('autocomplete', () => {
type TestArgs = [ type TestArgs = [
string, string,
Array<string | Partial<SuggestionRawDefinition>>, string[],
(string | number)?, (string | number)?,
Parameters<typeof createCustomCallbackMocks>? Parameters<typeof createCustomCallbackMocks>?
]; ];
const testSuggestionsFn = ( const testSuggestionsFn = (
statement: string, statement: string,
expected: Array<string | Partial<SuggestionRawDefinition>>, expected: string[],
triggerCharacter: string | number = '', triggerCharacter: string | number = '',
customCallbacksArgs: Parameters<typeof createCustomCallbackMocks> = [ customCallbacksArgs: Parameters<typeof createCustomCallbackMocks> = [
undefined, undefined,
@ -271,28 +270,20 @@ describe('autocomplete', () => {
async (text) => (text ? getAstAndSyntaxErrors(text) : { ast: [], errors: [] }), async (text) => (text ? getAstAndSyntaxErrors(text) : { ast: [], errors: [] }),
callbackMocks callbackMocks
); );
const suggestionInertTextSorted = suggestions
// simulate the editor behaviour for sorting suggestions
// copied from https://github.com/microsoft/vscode/blob/0a141d23179c76c5771df25a43546d9d9b6ed71c/src/vs/workbench/contrib/testing/browser/testingDecorations.ts#L971-L972
// still not sure how accurate this is...
.sort((a, b) => (a.sortText || a.label).localeCompare(b.sortText || b.label));
expect(suggestionInertTextSorted).toHaveLength(expected.length); const sortedSuggestions = suggestions.map((suggestion) => suggestion.text).sort();
for (const [index, receivedSuggestion] of suggestionInertTextSorted.entries()) { const sortedExpected = expected.sort();
if (typeof expected[index] !== 'object') {
expect(receivedSuggestion.text).toEqual(expected[index]); expect(sortedSuggestions).toEqual(sortedExpected);
} else {
// check all properties that are defined in the expected suggestion
for (const [key, value] of Object.entries(expected[index])) {
expect(receivedSuggestion[key as keyof SuggestionRawDefinition]).toEqual(value);
}
}
}
} }
); );
}; };
// Enrich the function to work with .only and .skip as regular test function // Enrich the function to work with .only and .skip as regular test function
//
// DO NOT CHANGE THE NAME OF THIS FUNCTION WITHOUT ALSO CHANGING
// THE LINTER RULE IN packages/kbn-eslint-config/typescript.js
//
const testSuggestions = Object.assign(testSuggestionsFn, { const testSuggestions = Object.assign(testSuggestionsFn, {
skip: (...args: TestArgs) => { skip: (...args: TestArgs) => {
const paddingArgs = ['', [undefined, undefined, undefined]].slice(args.length - 2); const paddingArgs = ['', [undefined, undefined, undefined]].slice(args.length - 2);
@ -616,6 +607,16 @@ describe('autocomplete', () => {
'any', 'any',
{ {
evalMath: true, evalMath: true,
grouping: false,
},
undefined,
undefined,
'by'
);
const allGroupingFunctions = getFunctionSignaturesByReturnType(
'stats',
'any',
{
grouping: true, grouping: true,
}, },
undefined, undefined,
@ -623,25 +624,26 @@ describe('autocomplete', () => {
'by' 'by'
); );
testSuggestions('from a | stats ', ['var0 =', ...allAggFunctions, ...allEvaFunctions]); testSuggestions('from a | stats ', ['var0 =', ...allAggFunctions, ...allEvaFunctions]);
testSuggestions('from a | stats a ', [ testSuggestions('from a | stats a ', ['= $0']);
{ text: '= $0', asSnippet: true, command: TRIGGER_SUGGESTION_COMMAND },
]);
testSuggestions('from a | stats a=', [...allAggFunctions, ...allEvaFunctions]); testSuggestions('from a | stats a=', [...allAggFunctions, ...allEvaFunctions]);
testSuggestions.only('from a | stats a=max(b) by ', [ testSuggestions('from a | stats a=max(b) by ', [
'var0 =', 'var0 =',
...getFieldNamesByType('any'), ...getFieldNamesByType('any'),
...allEvaFunctions, ...allEvaFunctions,
...allGroupingFunctions,
]); ]);
testSuggestions('from a | stats a=max(b) BY ', [ testSuggestions('from a | stats a=max(b) BY ', [
'var0 =', 'var0 =',
...getFieldNamesByType('any'), ...getFieldNamesByType('any'),
...allEvaFunctions, ...allEvaFunctions,
...allGroupingFunctions,
]); ]);
testSuggestions('from a | stats a=c by d ', [',', '|']); testSuggestions('from a | stats a=c by d ', [',', '|']);
testSuggestions('from a | stats a=c by d, ', [ testSuggestions('from a | stats a=c by d, ', [
'var0 =', 'var0 =',
...getFieldNamesByType('any'), ...getFieldNamesByType('any'),
...allEvaFunctions, ...allEvaFunctions,
...allGroupingFunctions,
]); ]);
testSuggestions('from a | stats a=max(b), ', [ testSuggestions('from a | stats a=max(b), ', [
'var0 =', 'var0 =',
@ -663,6 +665,7 @@ describe('autocomplete', () => {
'var0 =', 'var0 =',
...getFieldNamesByType('any'), ...getFieldNamesByType('any'),
...allEvaFunctions, ...allEvaFunctions,
...allGroupingFunctions,
]); ]);
testSuggestions('from a | stats a=min(b),', ['var0 =', ...allAggFunctions, ...allEvaFunctions]); testSuggestions('from a | stats a=min(b),', ['var0 =', ...allAggFunctions, ...allEvaFunctions]);
testSuggestions('from a | stats var0=min(b),var1=c,', [ testSuggestions('from a | stats var0=min(b),var1=c,', [
@ -705,19 +708,23 @@ describe('autocomplete', () => {
...getFieldNamesByType('number'), ...getFieldNamesByType('number'),
'`avg(b)`', '`avg(b)`',
...getFunctionSignaturesByReturnType('eval', 'number', { evalMath: true }), ...getFunctionSignaturesByReturnType('eval', 'number', { evalMath: true }),
...allGroupingFunctions,
]); ]);
testSuggestions('from a | stats avg(b) by var0 = ', [ testSuggestions('from a | stats avg(b) by var0 = ', [
...getFieldNamesByType('any'), ...getFieldNamesByType('any'),
...allEvaFunctions, ...allEvaFunctions,
...allGroupingFunctions,
]); ]);
testSuggestions('from a | stats avg(b) by c, ', [ testSuggestions('from a | stats avg(b) by c, ', [
'var0 =', 'var0 =',
...getFieldNamesByType('any'), ...getFieldNamesByType('any'),
...getFunctionSignaturesByReturnType('eval', 'any', { evalMath: true }), ...getFunctionSignaturesByReturnType('eval', 'any', { evalMath: true }),
...allGroupingFunctions,
]); ]);
testSuggestions('from a | stats avg(b) by c, var0 = ', [ testSuggestions('from a | stats avg(b) by c, var0 = ', [
...getFieldNamesByType('any'), ...getFieldNamesByType('any'),
...allEvaFunctions, ...allEvaFunctions,
...allGroupingFunctions,
]); ]);
testSuggestions('from a | stats avg(b) by numberField % 2 ', [',', '|']); testSuggestions('from a | stats avg(b) by numberField % 2 ', [',', '|']);
@ -1160,7 +1167,7 @@ describe('autocomplete', () => {
} }
} }
testSuggestions('from a | eval var0 = bucket(@timestamp,', []); testSuggestions('from a | eval var0 = bucket(@timestamp,', getUnitDuration(1));
describe('date math', () => { describe('date math', () => {
const dateSuggestions = timeLiterals.map(({ name }) => name); const dateSuggestions = timeLiterals.map(({ name }) => name);

View file

@ -289,7 +289,7 @@ export const buildNoPoliciesAvailableDefinition = (): SuggestionRawDefinition =>
}, },
}); });
function getUnitDuration(unit: number = 1) { export function getUnitDuration(unit: number = 1) {
const filteredTimeLiteral = timeLiterals.filter(({ name }) => { const filteredTimeLiteral = timeLiterals.filter(({ name }) => {
const result = /s$/.test(name); const result = /s$/.test(name);
return unit > 1 ? result : !result; return unit > 1 ? result : !result;
@ -297,6 +297,19 @@ function getUnitDuration(unit: number = 1) {
return filteredTimeLiteral.map(({ name }) => `${unit} ${name}`); return filteredTimeLiteral.map(({ name }) => `${unit} ${name}`);
} }
/**
* Given information about the current command and the parameter type, suggest
* some literals that may make sense.
*
* TODO this currently tries to cover both command-specific suggestions and type
* suggestions. We could consider separating the two... or just using parameter types
* and forgetting about command-specific suggestions altogether.
*
* Another thought... should literal suggestions be defined in the definitions file?
* That approach might allow for greater specificity in the suggestions and remove some
* "magical" logic. Maybe this is really the same thing as the literalOptions parameter
* definition property...
*/
export function getCompatibleLiterals(commandName: string, types: string[], names?: string[]) { export function getCompatibleLiterals(commandName: string, types: string[], names?: string[]) {
const suggestions: SuggestionRawDefinition[] = []; const suggestions: SuggestionRawDefinition[] = [];
if (types.includes('number')) { if (types.includes('number')) {

View file

@ -362,6 +362,10 @@ describe('validation logic', () => {
type TestArgs = [string, string[], string[]?]; type TestArgs = [string, string[], string[]?];
// Make only and skip work with our custom wrapper // Make only and skip work with our custom wrapper
//
// DO NOT CHANGE THE NAME OF THIS FUNCTION WITHOUT ALSO CHANGING
// THE LINTER RULE IN packages/kbn-eslint-config/typescript.js
//
const testErrorsAndWarnings = Object.assign(testErrorsAndWarningsFn, { const testErrorsAndWarnings = Object.assign(testErrorsAndWarningsFn, {
skip: (...args: TestArgs) => { skip: (...args: TestArgs) => {
const warningArgs = [[]].slice(args.length - 2); const warningArgs = [[]].slice(args.length - 2);