mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
parent
088f301598
commit
99f426c347
19 changed files with 5506 additions and 387670 deletions
|
@ -44,7 +44,7 @@ module.exports = {
|
|||
'filter',
|
||||
'ip_script_field_script_field',
|
||||
'long_script_field_script_field',
|
||||
'painless_test',
|
||||
'common',
|
||||
'processor_conditional',
|
||||
'score',
|
||||
'string_script_field_script_field',
|
||||
|
|
|
@ -69,8 +69,8 @@ function start(opts) {
|
|||
// Generate autocomplete definitions
|
||||
painlessContextFolderContents
|
||||
.filter((file) => {
|
||||
// Expected filename format: whitelist-<contextName>.json
|
||||
const contextName = file.split('.')[0].split('whitelist-').pop();
|
||||
// Expected filename format: painless-<contextName>.json
|
||||
const contextName = file.split('.')[0].split('painless-').pop();
|
||||
return supportedContexts.includes(contextName);
|
||||
})
|
||||
.forEach((file) => {
|
||||
|
@ -78,7 +78,8 @@ function start(opts) {
|
|||
const { name, classes: painlessClasses } = JSON.parse(
|
||||
readFileSync(join(esPainlessContextFolder, file), 'utf8')
|
||||
);
|
||||
const filePath = join(autocompleteOutputFolder, `${name}.json`);
|
||||
const contextName = name ? name : 'common'; // The common allowlist does not have a name associated to it.
|
||||
const filePath = join(autocompleteOutputFolder, `${contextName}.json`);
|
||||
const code = JSON.stringify(
|
||||
{ suggestions: createAutocompleteDefinitions(painlessClasses) },
|
||||
null,
|
||||
|
|
|
@ -33,7 +33,8 @@ const esPainlessContextFolder = join(
|
|||
'lang-painless',
|
||||
'src',
|
||||
'main',
|
||||
'generated'
|
||||
'generated',
|
||||
'whitelist-json'
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
|
@ -49,38 +49,6 @@ const getDisplayName = (name, imported) => {
|
|||
return displayName.replace('$', '.');
|
||||
};
|
||||
|
||||
/**
|
||||
* Filters the context data by primitives and returns an array of primitive names
|
||||
* The current data structure from ES does not indicate if a field is
|
||||
* a primitive or class, so we infer this by checking
|
||||
* that no methods or fields are defined
|
||||
* @param {string} contextData
|
||||
* @returns {Array<String>}
|
||||
*/
|
||||
const getPrimitives = (contextData) => {
|
||||
return contextData
|
||||
.filter(
|
||||
({
|
||||
static_fields: staticFields,
|
||||
fields,
|
||||
static_methods: staticMethods,
|
||||
methods,
|
||||
constructors,
|
||||
}) => {
|
||||
if (
|
||||
staticMethods.length === 0 &&
|
||||
methods.length === 0 &&
|
||||
staticFields.length === 0 &&
|
||||
fields.length === 0 &&
|
||||
constructors.length === 0
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
)
|
||||
.map((type) => type.name);
|
||||
};
|
||||
|
||||
/**
|
||||
* Given the method name, array of parameters, and return value,
|
||||
* we create a description of the method that will be
|
||||
|
@ -286,7 +254,6 @@ const createAutocompleteDefinitions = (painlessClasses) => {
|
|||
}) => {
|
||||
// The name is often prefixed by the Java package (e.g., Java.lang.Math) and needs to be removed
|
||||
const displayName = getDisplayName(name, imported);
|
||||
const isType = getPrimitives(painlessClasses).includes(name);
|
||||
|
||||
const properties = getPainlessClassToAutocomplete({
|
||||
staticFields,
|
||||
|
@ -299,8 +266,8 @@ const createAutocompleteDefinitions = (painlessClasses) => {
|
|||
|
||||
return {
|
||||
label: displayName,
|
||||
kind: isType ? 'type' : 'class',
|
||||
documentation: isType ? `Primitive: ${displayName}` : `Class: ${displayName}`,
|
||||
kind: 'class',
|
||||
documentation: `Class: ${displayName}`,
|
||||
insertText: displayName,
|
||||
properties: properties.length ? properties : undefined,
|
||||
constructorDefinition,
|
||||
|
@ -313,7 +280,6 @@ const createAutocompleteDefinitions = (painlessClasses) => {
|
|||
|
||||
module.exports = {
|
||||
getMethodDescription,
|
||||
getPrimitives,
|
||||
getPainlessClassToAutocomplete,
|
||||
createAutocompleteDefinitions,
|
||||
};
|
||||
|
|
|
@ -17,32 +17,13 @@
|
|||
* under the License.
|
||||
*/
|
||||
const {
|
||||
getPrimitives,
|
||||
getMethodDescription,
|
||||
getPainlessClassToAutocomplete,
|
||||
createAutocompleteDefinitions,
|
||||
} = require('./create_autocomplete_definitions');
|
||||
|
||||
// Snippet of sample data returned from GET _scripts/painless/_context?context=<context>
|
||||
// Snippet of sample data returned from https://github.com/elastic/elasticsearch/tree/master/modules/lang-painless/src/main/generated/whitelist-json
|
||||
const testContext = [
|
||||
{
|
||||
name: 'boolean',
|
||||
imported: true,
|
||||
constructors: [],
|
||||
static_methods: [],
|
||||
methods: [],
|
||||
static_fields: [],
|
||||
fields: [],
|
||||
},
|
||||
{
|
||||
name: 'int',
|
||||
imported: true,
|
||||
constructors: [],
|
||||
static_methods: [],
|
||||
methods: [],
|
||||
static_fields: [],
|
||||
fields: [],
|
||||
},
|
||||
{
|
||||
name: 'java.lang.Long',
|
||||
imported: true,
|
||||
|
@ -103,12 +84,6 @@ const testContext = [
|
|||
];
|
||||
|
||||
describe('Autocomplete utils', () => {
|
||||
describe('getPrimitives()', () => {
|
||||
test('returns an array of primitives', () => {
|
||||
expect(getPrimitives(testContext)).toEqual(['boolean', 'int']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMethodDescription()', () => {
|
||||
test('returns a string describing the method', () => {
|
||||
expect(getMethodDescription('pow', [['double', 'double']], ['double'])).toEqual(
|
||||
|
@ -128,7 +103,7 @@ describe('Autocomplete utils', () => {
|
|||
|
||||
describe('getPainlessClassToAutocomplete()', () => {
|
||||
test('returns the fields and methods associated with a class', () => {
|
||||
const mathClass = testContext[3];
|
||||
const mathClass = testContext[1];
|
||||
|
||||
const {
|
||||
static_fields: staticFields,
|
||||
|
@ -173,7 +148,7 @@ describe('Autocomplete utils', () => {
|
|||
});
|
||||
|
||||
test('removes duplicate methods', () => {
|
||||
const longClass = testContext[2];
|
||||
const longClass = testContext[0];
|
||||
|
||||
const {
|
||||
static_fields: staticFields,
|
||||
|
@ -251,22 +226,6 @@ describe('Autocomplete utils', () => {
|
|||
describe('createAutocompleteDefinitions()', () => {
|
||||
test('returns formatted autocomplete definitions', () => {
|
||||
expect(createAutocompleteDefinitions(testContext)).toEqual([
|
||||
{
|
||||
properties: undefined,
|
||||
constructorDefinition: undefined,
|
||||
documentation: 'Primitive: boolean',
|
||||
insertText: 'boolean',
|
||||
kind: 'type',
|
||||
label: 'boolean',
|
||||
},
|
||||
{
|
||||
properties: undefined,
|
||||
constructorDefinition: undefined,
|
||||
documentation: 'Primitive: int',
|
||||
insertText: 'int',
|
||||
kind: 'type',
|
||||
label: 'int',
|
||||
},
|
||||
{
|
||||
constructorDefinition: undefined,
|
||||
documentation: 'Class: Long',
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -20,7 +20,7 @@
|
|||
import * as stringScriptFieldScriptFieldContext from './string_script_field_script_field.json';
|
||||
import * as scoreContext from './score.json';
|
||||
import * as processorConditionalContext from './processor_conditional.json';
|
||||
import * as painlessTestContext from './painless_test.json';
|
||||
import * as commonContext from './common.json';
|
||||
import * as longScriptFieldScriptFieldContext from './long_script_field_script_field.json';
|
||||
import * as ipScriptFieldScriptFieldContext from './ip_script_field_script_field.json';
|
||||
import * as filterContext from './filter.json';
|
||||
|
@ -30,7 +30,7 @@ import * as booleanScriptFieldScriptFieldContext from './boolean_script_field_sc
|
|||
export { stringScriptFieldScriptFieldContext };
|
||||
export { scoreContext };
|
||||
export { processorConditionalContext };
|
||||
export { painlessTestContext };
|
||||
export { commonContext };
|
||||
export { longScriptFieldScriptFieldContext };
|
||||
export { ipScriptFieldScriptFieldContext };
|
||||
export { filterContext };
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -23,31 +23,15 @@ import {
|
|||
getStaticSuggestions,
|
||||
getFieldSuggestions,
|
||||
getClassMemberSuggestions,
|
||||
getPrimitives,
|
||||
getConstructorSuggestions,
|
||||
getKeywords,
|
||||
getTypeSuggestions,
|
||||
Suggestion,
|
||||
} from './autocomplete';
|
||||
|
||||
const keywords: PainlessCompletionItem[] = getKeywords();
|
||||
|
||||
const testSuggestions: Suggestion[] = [
|
||||
{
|
||||
properties: undefined,
|
||||
constructorDefinition: undefined,
|
||||
documentation: 'Primitive: boolean',
|
||||
insertText: 'boolean',
|
||||
kind: 'type',
|
||||
label: 'boolean',
|
||||
},
|
||||
{
|
||||
properties: undefined,
|
||||
constructorDefinition: undefined,
|
||||
documentation: 'Primitive: int',
|
||||
insertText: 'int',
|
||||
kind: 'type',
|
||||
label: 'int',
|
||||
},
|
||||
{
|
||||
properties: [
|
||||
{
|
||||
|
@ -97,18 +81,6 @@ describe('Autocomplete lib', () => {
|
|||
expect(getStaticSuggestions({ suggestions: testSuggestions })).toEqual({
|
||||
isIncomplete: false,
|
||||
suggestions: [
|
||||
{
|
||||
documentation: 'Primitive: boolean',
|
||||
insertText: 'boolean',
|
||||
kind: 'type',
|
||||
label: 'boolean',
|
||||
},
|
||||
{
|
||||
documentation: 'Primitive: int',
|
||||
insertText: 'int',
|
||||
kind: 'type',
|
||||
label: 'int',
|
||||
},
|
||||
{
|
||||
documentation: 'Class: Math',
|
||||
insertText: 'Math',
|
||||
|
@ -122,6 +94,7 @@ describe('Autocomplete lib', () => {
|
|||
label: 'ArithmeticException',
|
||||
},
|
||||
...keywords,
|
||||
...getTypeSuggestions(),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
@ -149,12 +122,6 @@ describe('Autocomplete lib', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('getPrimitives()', () => {
|
||||
test('returns primitive values', () => {
|
||||
expect(getPrimitives(testSuggestions)).toEqual(['boolean', 'int']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getClassMemberSuggestions()', () => {
|
||||
test('returns class member suggestions', () => {
|
||||
expect(getClassMemberSuggestions(testSuggestions, 'Math')).toEqual({
|
||||
|
|
|
@ -27,7 +27,7 @@ import {
|
|||
} from '../../types';
|
||||
|
||||
import {
|
||||
painlessTestContext,
|
||||
commonContext,
|
||||
scoreContext,
|
||||
filterContext,
|
||||
booleanScriptFieldScriptFieldContext,
|
||||
|
@ -81,6 +81,17 @@ export const getKeywords = (): PainlessCompletionItem[] => {
|
|||
return allKeywords;
|
||||
};
|
||||
|
||||
export const getTypeSuggestions = (): PainlessCompletionItem[] => {
|
||||
return lexerRules.primitives.map((primitive) => {
|
||||
return {
|
||||
label: primitive,
|
||||
kind: 'type',
|
||||
documentation: `Type: ${primitive}`,
|
||||
insertText: primitive,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const runtimeContexts: PainlessContext[] = [
|
||||
'boolean_script_field_script_field',
|
||||
'date_script_field',
|
||||
|
@ -91,7 +102,7 @@ const runtimeContexts: PainlessContext[] = [
|
|||
];
|
||||
|
||||
const mapContextToData: { [key: string]: { suggestions: any[] } } = {
|
||||
painless_test: painlessTestContext,
|
||||
painless_test: commonContext,
|
||||
score: scoreContext,
|
||||
filter: filterContext,
|
||||
boolean_script_field_script_field: booleanScriptFieldScriptFieldContext,
|
||||
|
@ -118,6 +129,7 @@ export const getStaticSuggestions = ({
|
|||
});
|
||||
|
||||
const keywords = getKeywords();
|
||||
const typeSuggestions = getTypeSuggestions();
|
||||
|
||||
let keywordSuggestions: PainlessCompletionItem[] = hasFields
|
||||
? [
|
||||
|
@ -156,14 +168,10 @@ export const getStaticSuggestions = ({
|
|||
|
||||
return {
|
||||
isIncomplete: false,
|
||||
suggestions: [...classSuggestions, ...keywordSuggestions],
|
||||
suggestions: [...classSuggestions, ...keywordSuggestions, ...typeSuggestions],
|
||||
};
|
||||
};
|
||||
|
||||
export const getPrimitives = (suggestions: Suggestion[]): string[] => {
|
||||
return suggestions.filter((suggestion) => suggestion.kind === 'type').map((type) => type.label);
|
||||
};
|
||||
|
||||
export const getClassMemberSuggestions = (
|
||||
suggestions: Suggestion[],
|
||||
className: string
|
||||
|
@ -224,10 +232,16 @@ export const getAutocompleteSuggestions = (
|
|||
words: string[],
|
||||
fields?: PainlessAutocompleteField[]
|
||||
): PainlessCompletionResult => {
|
||||
const suggestions = mapContextToData[painlessContext].suggestions;
|
||||
// Unique suggestions based on context
|
||||
const contextSuggestions = mapContextToData[painlessContext].suggestions;
|
||||
// Enhance suggestions with common classes that exist in all contexts
|
||||
// "painless_test" is the exception since it equals the common suggestions
|
||||
const suggestions =
|
||||
painlessContext === 'painless_test'
|
||||
? contextSuggestions
|
||||
: contextSuggestions.concat(commonContext.suggestions);
|
||||
// What the user is currently typing
|
||||
const activeTyping = words[words.length - 1];
|
||||
const primitives = getPrimitives(suggestions);
|
||||
// This logic may end up needing to be more robust as we integrate autocomplete into more editors
|
||||
// For now, we're assuming there is a list of painless contexts that are only applicable in runtime fields
|
||||
const isRuntimeContext = runtimeContexts.includes(painlessContext);
|
||||
|
@ -247,7 +261,7 @@ export const getAutocompleteSuggestions = (
|
|||
} else if (isAccessingProperty(activeTyping)) {
|
||||
const className = activeTyping.substring(0, activeTyping.length - 1).split('.')[0];
|
||||
autocompleteSuggestions = getClassMemberSuggestions(suggestions, className);
|
||||
} else if (showStaticSuggestions(activeTyping, words, primitives)) {
|
||||
} else if (showStaticSuggestions(activeTyping, words, lexerRules.primitives)) {
|
||||
autocompleteSuggestions = getStaticSuggestions({ suggestions, hasFields, isRuntimeContext });
|
||||
}
|
||||
return autocompleteSuggestions;
|
||||
|
|
|
@ -37,8 +37,8 @@ export const isAccessingProperty = (activeTyping: string): boolean => {
|
|||
* If the preceding word is a primitive type, e.g., "boolean",
|
||||
* we assume the user is declaring a variable and will skip autocomplete
|
||||
*
|
||||
* Note: this isn't entirely exhaustive. For example, "def myVar =" is not included in context
|
||||
* It's also acceptable to use a class as a type, e.g., "String myVar ="
|
||||
* Note: this isn't entirely exhaustive.
|
||||
* For example, you may use a class as a type, e.g., "String myVar ="
|
||||
*/
|
||||
export const hasDeclaredType = (activeLineWords: string[], primitives: string[]): boolean => {
|
||||
return activeLineWords.length === 2 && primitives.includes(activeLineWords[0]);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue