fix: Improve Autocomplete for local vars, JSObject & ButtonGroup Widget
Add Autocomplete support for - local variables - JSObjects - ButtonGroupWidget Remove Autocomplete suggestion for - `eval` - undefined global values like `tabs`
This commit is contained in:
parent
1a6936435d
commit
e255593e28
|
|
@ -2,10 +2,10 @@ import { ObjectsRegistry } from "../../../../support/Objects/Registry";
|
||||||
|
|
||||||
const {
|
const {
|
||||||
AggregateHelper: agHelper,
|
AggregateHelper: agHelper,
|
||||||
|
|
||||||
|
DeployMode: deployMode,
|
||||||
EntityExplorer: ee,
|
EntityExplorer: ee,
|
||||||
JSEditor: jsEditor,
|
JSEditor: jsEditor,
|
||||||
CommonLocators: locator,
|
|
||||||
DeployMode: deployMode,
|
|
||||||
PropertyPane: propPane,
|
PropertyPane: propPane,
|
||||||
} = ObjectsRegistry;
|
} = ObjectsRegistry;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
import { WIDGET } from "../../../../locators/WidgetLocators";
|
||||||
|
import { ObjectsRegistry } from "../../../../support/Objects/Registry";
|
||||||
|
const explorer = require("../../../../locators/explorerlocators.json");
|
||||||
|
|
||||||
|
const { CommonLocators, EntityExplorer, JSEditor: jsEditor } = ObjectsRegistry;
|
||||||
|
|
||||||
|
const jsObjectBody = `export default {
|
||||||
|
myVar1: [],
|
||||||
|
myVar2: {},
|
||||||
|
myFun1(){
|
||||||
|
|
||||||
|
},
|
||||||
|
myFun2: async () => {
|
||||||
|
//use async-await or promises
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
describe("Autocomplete tests", () => {
|
||||||
|
before(() => {
|
||||||
|
cy.get(explorer.addWidget).click();
|
||||||
|
EntityExplorer.DragDropWidgetNVerify(WIDGET.BUTTON_GROUP_WIDGET, 300, 500);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("1. ButtonGroup autocomplete & Eval shouldn't show up", () => {
|
||||||
|
// create js object
|
||||||
|
jsEditor.CreateJSObject(jsObjectBody, {
|
||||||
|
paste: true,
|
||||||
|
completeReplace: true,
|
||||||
|
toRun: false,
|
||||||
|
shouldCreateNewJSObj: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const lineNumber = 5;
|
||||||
|
cy.get(`:nth-child(${lineNumber}) > .CodeMirror-line`).click();
|
||||||
|
|
||||||
|
cy.get(CommonLocators._codeMirrorTextArea)
|
||||||
|
.focus()
|
||||||
|
.type(`ButtonGroup1.`);
|
||||||
|
|
||||||
|
cy.get(`.CodeMirror-hints > :nth-child(1)`).contains("groupButtons");
|
||||||
|
|
||||||
|
cy.get(CommonLocators._codeMirrorTextArea)
|
||||||
|
.focus()
|
||||||
|
.type(`groupButtons.`);
|
||||||
|
|
||||||
|
cy.get(`.CodeMirror-hints > :nth-child(1)`).contains("groupButton1");
|
||||||
|
|
||||||
|
cy.get(CommonLocators._codeMirrorTextArea).focus().type(`
|
||||||
|
eval`);
|
||||||
|
|
||||||
|
cy.get(`.CodeMirror-hints > :nth-child(1)`).should(
|
||||||
|
"not.have.value",
|
||||||
|
"eval()",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("2. Local variables autocompletion support", () => {
|
||||||
|
// create js object
|
||||||
|
jsEditor.CreateJSObject(jsObjectBody, {
|
||||||
|
paste: true,
|
||||||
|
completeReplace: true,
|
||||||
|
toRun: false,
|
||||||
|
shouldCreateNewJSObj: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const lineNumber = 5;
|
||||||
|
|
||||||
|
const array = [
|
||||||
|
{ label: "a", value: "b" },
|
||||||
|
{ label: "a", value: "b" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const codeToType = `
|
||||||
|
const arr = ${JSON.stringify(array)};
|
||||||
|
|
||||||
|
arr.map(callBack)
|
||||||
|
`;
|
||||||
|
|
||||||
|
// component re-render cause DOM element of cy.get to lost
|
||||||
|
// added wait to finish re-render before cy.get
|
||||||
|
cy.wait(100);
|
||||||
|
|
||||||
|
cy.get(`:nth-child(${lineNumber}) > .CodeMirror-line`).click();
|
||||||
|
|
||||||
|
cy.get(CommonLocators._codeMirrorTextArea)
|
||||||
|
.focus()
|
||||||
|
.type(`${codeToType}`, { parseSpecialCharSequences: false })
|
||||||
|
.type(`{upArrow}{upArrow}`)
|
||||||
|
.type(`const callBack = (item) => item.l`);
|
||||||
|
|
||||||
|
cy.get(`.CodeMirror-hints > :nth-child(1)`).contains("label");
|
||||||
|
|
||||||
|
cy.get(CommonLocators._codeMirrorTextArea)
|
||||||
|
.focus()
|
||||||
|
.type(`label`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("3. JSObject this. autocomplete", () => {
|
||||||
|
// create js object
|
||||||
|
jsEditor.CreateJSObject(jsObjectBody, {
|
||||||
|
paste: true,
|
||||||
|
completeReplace: true,
|
||||||
|
toRun: false,
|
||||||
|
shouldCreateNewJSObj: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const lineNumber = 5;
|
||||||
|
|
||||||
|
const codeToType = "this.";
|
||||||
|
|
||||||
|
cy.get(`:nth-child(${lineNumber}) > .CodeMirror-line`).click();
|
||||||
|
|
||||||
|
cy.get(CommonLocators._codeMirrorTextArea)
|
||||||
|
.focus()
|
||||||
|
.type(`${codeToType}`);
|
||||||
|
|
||||||
|
["myFun2()", "myVar1", "myVar2"].forEach((element, index) => {
|
||||||
|
cy.get(`.CodeMirror-hints > :nth-child(${index + 1})`).contains(element);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -5,6 +5,7 @@ export const WIDGET = {
|
||||||
CURRENCY_INPUT_WIDGET: "currencyinputwidget",
|
CURRENCY_INPUT_WIDGET: "currencyinputwidget",
|
||||||
BUTTON_WIDGET: "buttonwidget",
|
BUTTON_WIDGET: "buttonwidget",
|
||||||
MULTISELECT_WIDGET: "multiselectwidgetv2",
|
MULTISELECT_WIDGET: "multiselectwidgetv2",
|
||||||
|
BUTTON_GROUP_WIDGET: "buttongroupwidget",
|
||||||
TREESELECT_WIDGET: "singleselecttreewidget",
|
TREESELECT_WIDGET: "singleselecttreewidget",
|
||||||
TAB: "tabswidget",
|
TAB: "tabswidget",
|
||||||
TABLE: "tablewidgetv2",
|
TABLE: "tablewidgetv2",
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,14 @@ export interface ICreateJSObjectOptions {
|
||||||
completeReplace: boolean;
|
completeReplace: boolean;
|
||||||
toRun: boolean;
|
toRun: boolean;
|
||||||
shouldCreateNewJSObj: boolean;
|
shouldCreateNewJSObj: boolean;
|
||||||
|
lineNumber?: number;
|
||||||
}
|
}
|
||||||
const DEFAULT_CREATE_JS_OBJECT_OPTIONS = {
|
const DEFAULT_CREATE_JS_OBJECT_OPTIONS = {
|
||||||
paste: true,
|
paste: true,
|
||||||
completeReplace: false,
|
completeReplace: false,
|
||||||
toRun: true,
|
toRun: true,
|
||||||
shouldCreateNewJSObj: true,
|
shouldCreateNewJSObj: true,
|
||||||
|
lineNumber: 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
export class JSEditor {
|
export class JSEditor {
|
||||||
|
|
@ -118,30 +120,29 @@ export class JSEditor {
|
||||||
JSCode: string,
|
JSCode: string,
|
||||||
options: ICreateJSObjectOptions = DEFAULT_CREATE_JS_OBJECT_OPTIONS,
|
options: ICreateJSObjectOptions = DEFAULT_CREATE_JS_OBJECT_OPTIONS,
|
||||||
) {
|
) {
|
||||||
const { completeReplace, paste, shouldCreateNewJSObj, toRun } = options;
|
const {
|
||||||
|
completeReplace,
|
||||||
|
lineNumber = 4,
|
||||||
|
paste,
|
||||||
|
shouldCreateNewJSObj,
|
||||||
|
toRun,
|
||||||
|
} = options;
|
||||||
|
|
||||||
shouldCreateNewJSObj && this.NavigateToNewJSEditor();
|
shouldCreateNewJSObj && this.NavigateToNewJSEditor();
|
||||||
if (!completeReplace) {
|
if (!completeReplace) {
|
||||||
|
const downKeys = Array.from(new Array(lineNumber), () => "{downarrow}")
|
||||||
|
.toString()
|
||||||
|
.replaceAll(",", "");
|
||||||
cy.get(this.locator._codeMirrorTextArea)
|
cy.get(this.locator._codeMirrorTextArea)
|
||||||
.first()
|
.first()
|
||||||
.focus()
|
.focus()
|
||||||
.type("{downarrow}{downarrow}{downarrow}{downarrow} ");
|
.type(`${downKeys} `);
|
||||||
} else {
|
} else {
|
||||||
cy.get(this.locator._codeMirrorTextArea)
|
cy.get(this.locator._codeMirrorTextArea)
|
||||||
.first()
|
.first()
|
||||||
.focus()
|
.focus()
|
||||||
.type(this.selectAllJSObjectContentShortcut)
|
.type(this.selectAllJSObjectContentShortcut)
|
||||||
.type("{backspace}", { force: true });
|
.type("{backspace}", { force: true });
|
||||||
|
|
||||||
// .type("{uparrow}", { force: true })
|
|
||||||
// .type("{ctrl}{shift}{downarrow}", { force: true })
|
|
||||||
// .type("{del}",{ force: true });
|
|
||||||
|
|
||||||
// cy.get(this.locator._codthis.eeditorTarget).contains('export').click().closest(this.locator._codthis.eeditorTarget)
|
|
||||||
// .type("{uparrow}", { force: true })
|
|
||||||
// .type("{ctrl}{shift}{downarrow}", { force: true })
|
|
||||||
// .type("{backspace}",{ force: true });
|
|
||||||
//.type("{downarrow}{downarrow}{downarrow}{downarrow}{downarrow}{downarrow}{downarrow}{downarrow}{downarrow}{downarrow} ")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cy.get(this.locator._codeMirrorTextArea)
|
cy.get(this.locator._codeMirrorTextArea)
|
||||||
|
|
|
||||||
|
|
@ -45,9 +45,10 @@ export const EditorThemes: Record<EditorTheme, string> = {
|
||||||
export type FieldEntityInformation = {
|
export type FieldEntityInformation = {
|
||||||
entityName?: string;
|
entityName?: string;
|
||||||
expectedType?: AutocompleteDataType;
|
expectedType?: AutocompleteDataType;
|
||||||
entityType?: ENTITY_TYPE.ACTION | ENTITY_TYPE.WIDGET | ENTITY_TYPE.JSACTION;
|
entityType?: ENTITY_TYPE;
|
||||||
entityId?: string;
|
entityId?: string;
|
||||||
propertyPath?: string;
|
propertyPath?: string;
|
||||||
|
blockCompletions?: Array<{ parentPath: string; subPath: string }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type HintHelper = (
|
export type HintHelper = (
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,20 @@ export const bindingHint: HintHelper = (editor, dataTree, customDataTree) => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
showHint: (editor: CodeMirror.Editor, entityInformation): boolean => {
|
showHint: (
|
||||||
TernServer.setEntityInformation(entityInformation);
|
editor: CodeMirror.Editor,
|
||||||
|
entityInformation,
|
||||||
|
additionalData,
|
||||||
|
): boolean => {
|
||||||
|
if (additionalData && additionalData.blockCompletions) {
|
||||||
|
TernServer.setEntityInformation({
|
||||||
|
...entityInformation,
|
||||||
|
blockCompletions: additionalData.blockCompletions,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
TernServer.setEntityInformation(entityInformation);
|
||||||
|
}
|
||||||
|
|
||||||
const entityType = entityInformation?.entityType;
|
const entityType = entityInformation?.entityType;
|
||||||
let shouldShow = false;
|
let shouldShow = false;
|
||||||
if (entityType === ENTITY_TYPE.JSACTION) {
|
if (entityType === ENTITY_TYPE.JSACTION) {
|
||||||
|
|
|
||||||
|
|
@ -101,17 +101,8 @@ import { getMoveCursorLeftKey } from "./utils/cursorLeftMovement";
|
||||||
import { interactionAnalyticsEvent } from "utils/AppsmithUtils";
|
import { interactionAnalyticsEvent } from "utils/AppsmithUtils";
|
||||||
import { AdditionalDynamicDataTree } from "utils/autocomplete/customTreeTypeDefCreator";
|
import { AdditionalDynamicDataTree } from "utils/autocomplete/customTreeTypeDefCreator";
|
||||||
|
|
||||||
interface ReduxStateProps {
|
type ReduxStateProps = ReturnType<typeof mapStateToProps>;
|
||||||
datasources: any;
|
type ReduxDispatchProps = ReturnType<typeof mapDispatchToProps>;
|
||||||
dynamicData: DataTree;
|
|
||||||
pluginIdToImageLocation: Record<string, string>;
|
|
||||||
recentEntities: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ReduxDispatchProps {
|
|
||||||
executeCommand: (payload: any) => void;
|
|
||||||
startingEntityUpdation: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CodeEditorExpected = {
|
export type CodeEditorExpected = {
|
||||||
type: string;
|
type: string;
|
||||||
|
|
@ -141,6 +132,7 @@ export type EditorStyleProps = {
|
||||||
evaluationSubstitutionType?: EvaluationSubstitutionType;
|
evaluationSubstitutionType?: EvaluationSubstitutionType;
|
||||||
popperPlacement?: Placement;
|
popperPlacement?: Placement;
|
||||||
popperZIndex?: Indices;
|
popperZIndex?: Indices;
|
||||||
|
blockCompletions?: FieldEntityInformation["blockCompletions"];
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* line => Line to which the gutter is added
|
* line => Line to which the gutter is added
|
||||||
|
|
@ -190,9 +182,7 @@ export type EditorProps = EditorStyleProps &
|
||||||
customGutter?: CodeEditorGutter;
|
customGutter?: CodeEditorGutter;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = ReduxStateProps &
|
interface Props extends ReduxStateProps, EditorProps, ReduxDispatchProps {}
|
||||||
EditorProps &
|
|
||||||
ReduxDispatchProps & { dispatch?: () => void };
|
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
isFocused: boolean;
|
isFocused: boolean;
|
||||||
|
|
@ -525,12 +515,16 @@ class CodeEditor extends Component<Props, State> {
|
||||||
|
|
||||||
handleEditorFocus = (cm: CodeMirror.Editor) => {
|
handleEditorFocus = (cm: CodeMirror.Editor) => {
|
||||||
this.setState({ isFocused: true });
|
this.setState({ isFocused: true });
|
||||||
|
|
||||||
if (!cm.state.completionActive) {
|
if (!cm.state.completionActive) {
|
||||||
const entityInformation: FieldEntityInformation = this.getEntityInformation();
|
const entityInformation = this.getEntityInformation();
|
||||||
|
const { blockCompletions } = this.props;
|
||||||
this.hinters
|
this.hinters
|
||||||
.filter((hinter) => hinter.fireOnFocus)
|
.filter((hinter) => hinter.fireOnFocus)
|
||||||
.forEach(
|
.forEach(
|
||||||
(hinter) => hinter.showHint && hinter.showHint(cm, entityInformation),
|
(hinter) =>
|
||||||
|
hinter.showHint &&
|
||||||
|
hinter.showHint(cm, entityInformation, blockCompletions),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -656,10 +650,12 @@ class CodeEditor extends Component<Props, State> {
|
||||||
|
|
||||||
handleAutocompleteVisibility = (cm: CodeMirror.Editor) => {
|
handleAutocompleteVisibility = (cm: CodeMirror.Editor) => {
|
||||||
if (!this.state.isFocused) return;
|
if (!this.state.isFocused) return;
|
||||||
const entityInformation: FieldEntityInformation = this.getEntityInformation();
|
const entityInformation = this.getEntityInformation();
|
||||||
|
const { blockCompletions } = this.props;
|
||||||
let hinterOpen = false;
|
let hinterOpen = false;
|
||||||
for (let i = 0; i < this.hinters.length; i++) {
|
for (let i = 0; i < this.hinters.length; i++) {
|
||||||
hinterOpen = this.hinters[i].showHint(cm, entityInformation, {
|
hinterOpen = this.hinters[i].showHint(cm, entityInformation, {
|
||||||
|
blockCompletions,
|
||||||
datasources: this.props.datasources.list,
|
datasources: this.props.datasources.list,
|
||||||
pluginIdToImageLocation: this.props.pluginIdToImageLocation,
|
pluginIdToImageLocation: this.props.pluginIdToImageLocation,
|
||||||
recentEntities: this.props.recentEntities,
|
recentEntities: this.props.recentEntities,
|
||||||
|
|
@ -957,14 +953,14 @@ class CodeEditor extends Component<Props, State> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state: AppState): ReduxStateProps => ({
|
const mapStateToProps = (state: AppState) => ({
|
||||||
dynamicData: getDataTreeForAutocomplete(state),
|
dynamicData: getDataTreeForAutocomplete(state),
|
||||||
datasources: state.entities.datasources,
|
datasources: state.entities.datasources,
|
||||||
pluginIdToImageLocation: getPluginIdToImageLocation(state),
|
pluginIdToImageLocation: getPluginIdToImageLocation(state),
|
||||||
recentEntities: getRecentEntityIds(state),
|
recentEntities: getRecentEntityIds(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch: any): ReduxDispatchProps => ({
|
const mapDispatchToProps = (dispatch: any) => ({
|
||||||
executeCommand: (payload: SlashCommandPayload) =>
|
executeCommand: (payload: SlashCommandPayload) =>
|
||||||
dispatch(executeCommandAction(payload)),
|
dispatch(executeCommandAction(payload)),
|
||||||
startingEntityUpdation: () => dispatch(startingEntityUpdation()),
|
startingEntityUpdation: () => dispatch(startingEntityUpdation()),
|
||||||
|
|
|
||||||
|
|
@ -1227,11 +1227,6 @@
|
||||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/isFinite",
|
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/isFinite",
|
||||||
"!doc": "Determines whether the passed value is a finite number."
|
"!doc": "Determines whether the passed value is a finite number."
|
||||||
},
|
},
|
||||||
"eval": {
|
|
||||||
"!type": "fn(code: string) -> ?",
|
|
||||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/eval",
|
|
||||||
"!doc": "Evaluates JavaScript code represented as a string."
|
|
||||||
},
|
|
||||||
"encodeURI": {
|
"encodeURI": {
|
||||||
"!type": "fn(uri: string) -> string",
|
"!type": "fn(uri: string) -> string",
|
||||||
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/encodeURI",
|
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/encodeURI",
|
||||||
|
|
|
||||||
|
|
@ -181,6 +181,24 @@ function JSEditorForm({ jsCollection: currentJSCollection }: Props) {
|
||||||
}
|
}
|
||||||
setSelectedJSActionOption(getJSActionOption(activeJSAction, jsActions));
|
setSelectedJSActionOption(getJSActionOption(activeJSAction, jsActions));
|
||||||
}, [parseErrors, jsActions, activeJSActionId]);
|
}, [parseErrors, jsActions, activeJSActionId]);
|
||||||
|
|
||||||
|
const blockCompletions = useMemo(() => {
|
||||||
|
if (selectedJSActionOption.label) {
|
||||||
|
const funcName = `${selectedJSActionOption.label}()`;
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
parentPath: "this",
|
||||||
|
subPath: funcName,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
parentPath: currentJSCollection.name,
|
||||||
|
subPath: funcName,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}, [selectedJSActionOption.label, currentJSCollection.name]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
<JSObjectHotKeys runActiveJSFunction={handleRunAction}>
|
<JSObjectHotKeys runActiveJSFunction={handleRunAction}>
|
||||||
|
|
@ -224,6 +242,7 @@ function JSEditorForm({ jsCollection: currentJSCollection }: Props) {
|
||||||
title: "Code",
|
title: "Code",
|
||||||
panelComponent: (
|
panelComponent: (
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
|
blockCompletions={blockCompletions}
|
||||||
className={"js-editor"}
|
className={"js-editor"}
|
||||||
customGutter={JSGutters}
|
customGutter={JSGutters}
|
||||||
dataTreePath={`${currentJSCollection.name}.body`}
|
dataTreePath={`${currentJSCollection.name}.body`}
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,33 @@ export class AndRule implements AutocompleteRule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set score to -Infinity for paths to be blocked from autocompletion
|
||||||
|
* Max score - 0
|
||||||
|
* Min score - -Infinity
|
||||||
|
*/
|
||||||
|
class BlockSuggestionsRule implements AutocompleteRule {
|
||||||
|
static threshold = -Infinity;
|
||||||
|
|
||||||
|
computeScore(completion: Completion): number {
|
||||||
|
let score = 0;
|
||||||
|
const { currentFieldInfo } = AutocompleteSorter;
|
||||||
|
const { blockCompletions } = currentFieldInfo;
|
||||||
|
|
||||||
|
if (blockCompletions) {
|
||||||
|
for (let index = 0; index < blockCompletions.length; index++) {
|
||||||
|
const { subPath } = blockCompletions[index];
|
||||||
|
if (completion.text === subPath) {
|
||||||
|
score = BlockSuggestionsRule.threshold;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set score to -Infinity for suggestions like Table1.selectedRow.address
|
* Set score to -Infinity for suggestions like Table1.selectedRow.address
|
||||||
* Max score - 0
|
* Max score - 0
|
||||||
|
|
@ -252,6 +279,7 @@ export class ScoredCompletion {
|
||||||
new DataTreeFunctionRule(),
|
new DataTreeFunctionRule(),
|
||||||
new JSLibraryRule(),
|
new JSLibraryRule(),
|
||||||
new GlobalJSRule(),
|
new GlobalJSRule(),
|
||||||
|
new BlockSuggestionsRule(),
|
||||||
];
|
];
|
||||||
completion: Completion;
|
completion: Completion;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import {
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { EVALUATION_PATH } from "utils/DynamicBindingUtils";
|
import { EVALUATION_PATH } from "utils/DynamicBindingUtils";
|
||||||
import { JSCollectionData } from "reducers/entityReducers/jsActionsReducer";
|
import { JSCollectionData } from "reducers/entityReducers/jsActionsReducer";
|
||||||
|
import { ButtonGroupWidgetProps } from "widgets/ButtonGroupWidget/widget";
|
||||||
|
|
||||||
const isVisible = {
|
const isVisible = {
|
||||||
"!type": "bool",
|
"!type": "bool",
|
||||||
|
|
@ -173,7 +174,7 @@ export const entityDefinitions = {
|
||||||
"!url": "https://docs.appsmith.com/widget-reference/dropdown",
|
"!url": "https://docs.appsmith.com/widget-reference/dropdown",
|
||||||
},
|
},
|
||||||
isDisabled: "bool",
|
isDisabled: "bool",
|
||||||
options: "[dropdownOption]",
|
options: "[$__dropdownOption__$]",
|
||||||
},
|
},
|
||||||
SELECT_WIDGET: {
|
SELECT_WIDGET: {
|
||||||
"!doc":
|
"!doc":
|
||||||
|
|
@ -195,7 +196,7 @@ export const entityDefinitions = {
|
||||||
"!url": "https://docs.appsmith.com/widget-reference/dropdown",
|
"!url": "https://docs.appsmith.com/widget-reference/dropdown",
|
||||||
},
|
},
|
||||||
isDisabled: "bool",
|
isDisabled: "bool",
|
||||||
options: "[dropdownOption]",
|
options: "[$__dropdownOption__$]",
|
||||||
},
|
},
|
||||||
MULTI_SELECT_WIDGET: {
|
MULTI_SELECT_WIDGET: {
|
||||||
"!doc":
|
"!doc":
|
||||||
|
|
@ -217,7 +218,7 @@ export const entityDefinitions = {
|
||||||
"!url": "https://docs.appsmith.com/widget-reference/dropdown",
|
"!url": "https://docs.appsmith.com/widget-reference/dropdown",
|
||||||
},
|
},
|
||||||
isDisabled: "bool",
|
isDisabled: "bool",
|
||||||
options: "[dropdownOption]",
|
options: "[$__dropdownOption__$]",
|
||||||
},
|
},
|
||||||
MULTI_SELECT_WIDGET_V2: {
|
MULTI_SELECT_WIDGET_V2: {
|
||||||
"!doc":
|
"!doc":
|
||||||
|
|
@ -239,7 +240,7 @@ export const entityDefinitions = {
|
||||||
"!url": "https://docs.appsmith.com/widget-reference/dropdown",
|
"!url": "https://docs.appsmith.com/widget-reference/dropdown",
|
||||||
},
|
},
|
||||||
isDisabled: "bool",
|
isDisabled: "bool",
|
||||||
options: "[dropdownOption]",
|
options: "[$__dropdownOption__$]",
|
||||||
},
|
},
|
||||||
IMAGE_WIDGET: {
|
IMAGE_WIDGET: {
|
||||||
"!doc":
|
"!doc":
|
||||||
|
|
@ -264,6 +265,14 @@ export const entityDefinitions = {
|
||||||
isDisabled: "bool",
|
isDisabled: "bool",
|
||||||
recaptchaToken: "string",
|
recaptchaToken: "string",
|
||||||
},
|
},
|
||||||
|
BUTTON_GROUP_WIDGET: (widget: ButtonGroupWidgetProps) => {
|
||||||
|
return {
|
||||||
|
"!doc":
|
||||||
|
"The Button group widget represents a set of buttons in a group. Group can have simple buttons or menu buttons with drop-down items.",
|
||||||
|
"!url": "https://docs.appsmith.com/widget-reference/button-group",
|
||||||
|
groupButtons: generateTypeDef(widget.groupButtons),
|
||||||
|
};
|
||||||
|
},
|
||||||
DATE_PICKER_WIDGET: {
|
DATE_PICKER_WIDGET: {
|
||||||
"!doc":
|
"!doc":
|
||||||
"Datepicker is used to capture the date and time from a user. It can be used to filter data base on the input date range as well as to capture personal information such as date of birth",
|
"Datepicker is used to capture the date and time from a user. It can be used to filter data base on the input date range as well as to capture personal information such as date of birth",
|
||||||
|
|
@ -302,7 +311,7 @@ export const entityDefinitions = {
|
||||||
"Radio widget lets the user choose only one option from a predefined set of options. It is quite similar to a SingleSelect Dropdown in its functionality",
|
"Radio widget lets the user choose only one option from a predefined set of options. It is quite similar to a SingleSelect Dropdown in its functionality",
|
||||||
"!url": "https://docs.appsmith.com/widget-reference/radio",
|
"!url": "https://docs.appsmith.com/widget-reference/radio",
|
||||||
isVisible: isVisible,
|
isVisible: isVisible,
|
||||||
options: "[dropdownOption]",
|
options: "[$__dropdownOption__$]",
|
||||||
selectedOptionValue: "string",
|
selectedOptionValue: "string",
|
||||||
isRequired: "bool",
|
isRequired: "bool",
|
||||||
},
|
},
|
||||||
|
|
@ -324,10 +333,13 @@ export const entityDefinitions = {
|
||||||
"Chart widget is used to view the graphical representation of your data. Chart is the go-to widget for your data visualisation needs.",
|
"Chart widget is used to view the graphical representation of your data. Chart is the go-to widget for your data visualisation needs.",
|
||||||
"!url": "https://docs.appsmith.com/widget-reference/chart",
|
"!url": "https://docs.appsmith.com/widget-reference/chart",
|
||||||
isVisible: isVisible,
|
isVisible: isVisible,
|
||||||
chartData: "chartData",
|
chartData: {
|
||||||
|
seriesName: "string",
|
||||||
|
data: "[$__chartDataPoint__$]",
|
||||||
|
},
|
||||||
xAxisName: "string",
|
xAxisName: "string",
|
||||||
yAxisName: "string",
|
yAxisName: "string",
|
||||||
selectedDataPoint: "chartDataPoint",
|
selectedDataPoint: "$__chartDataPoint__$",
|
||||||
},
|
},
|
||||||
FORM_WIDGET: (widget: any) => ({
|
FORM_WIDGET: (widget: any) => ({
|
||||||
"!doc":
|
"!doc":
|
||||||
|
|
@ -348,16 +360,25 @@ export const entityDefinitions = {
|
||||||
},
|
},
|
||||||
MAP_WIDGET: {
|
MAP_WIDGET: {
|
||||||
isVisible: isVisible,
|
isVisible: isVisible,
|
||||||
center: "latLong",
|
center: {
|
||||||
markers: "[mapMarker]",
|
lat: "number",
|
||||||
selectedMarker: "mapMarker",
|
long: "number",
|
||||||
|
title: "string",
|
||||||
|
},
|
||||||
|
markers: "[$__mapMarker__$]",
|
||||||
|
selectedMarker: {
|
||||||
|
lat: "number",
|
||||||
|
long: "number",
|
||||||
|
title: "string",
|
||||||
|
description: "string",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
FILE_PICKER_WIDGET: {
|
FILE_PICKER_WIDGET: {
|
||||||
"!doc":
|
"!doc":
|
||||||
"Filepicker widget is used to allow users to upload files from their local machines to any cloud storage via API. Cloudinary and Amazon S3 have simple APIs for cloud storage uploads",
|
"Filepicker widget is used to allow users to upload files from their local machines to any cloud storage via API. Cloudinary and Amazon S3 have simple APIs for cloud storage uploads",
|
||||||
"!url": "https://docs.appsmith.com/widget-reference/filepicker",
|
"!url": "https://docs.appsmith.com/widget-reference/filepicker",
|
||||||
isVisible: isVisible,
|
isVisible: isVisible,
|
||||||
files: "[file]",
|
files: "[$__file__$]",
|
||||||
isDisabled: "bool",
|
isDisabled: "bool",
|
||||||
},
|
},
|
||||||
FILE_PICKER_WIDGET_V2: {
|
FILE_PICKER_WIDGET_V2: {
|
||||||
|
|
@ -365,7 +386,7 @@ export const entityDefinitions = {
|
||||||
"Filepicker widget is used to allow users to upload files from their local machines to any cloud storage via API. Cloudinary and Amazon S3 have simple APIs for cloud storage uploads",
|
"Filepicker widget is used to allow users to upload files from their local machines to any cloud storage via API. Cloudinary and Amazon S3 have simple APIs for cloud storage uploads",
|
||||||
"!url": "https://docs.appsmith.com/widget-reference/filepicker",
|
"!url": "https://docs.appsmith.com/widget-reference/filepicker",
|
||||||
isVisible: isVisible,
|
isVisible: isVisible,
|
||||||
files: "[file]",
|
files: "[$__file__$]",
|
||||||
isDisabled: "bool",
|
isDisabled: "bool",
|
||||||
},
|
},
|
||||||
LIST_WIDGET: (widget: any) => ({
|
LIST_WIDGET: (widget: any) => ({
|
||||||
|
|
@ -436,7 +457,7 @@ export const entityDefinitions = {
|
||||||
},
|
},
|
||||||
isDisabled: "bool",
|
isDisabled: "bool",
|
||||||
isValid: "bool",
|
isValid: "bool",
|
||||||
options: "[dropdownOption]",
|
options: "[$__dropdownOption__$]",
|
||||||
},
|
},
|
||||||
MULTI_SELECT_TREE_WIDGET: {
|
MULTI_SELECT_TREE_WIDGET: {
|
||||||
"!doc":
|
"!doc":
|
||||||
|
|
@ -455,7 +476,7 @@ export const entityDefinitions = {
|
||||||
},
|
},
|
||||||
isDisabled: "bool",
|
isDisabled: "bool",
|
||||||
isValid: "bool",
|
isValid: "bool",
|
||||||
options: "[dropdownOption]",
|
options: "[$__dropdownOption__$]",
|
||||||
},
|
},
|
||||||
ICON_BUTTON_WIDGET: {
|
ICON_BUTTON_WIDGET: {
|
||||||
"!doc":
|
"!doc":
|
||||||
|
|
@ -470,7 +491,7 @@ export const entityDefinitions = {
|
||||||
isVisible: isVisible,
|
isVisible: isVisible,
|
||||||
isDisabled: "bool",
|
isDisabled: "bool",
|
||||||
isValid: "bool",
|
isValid: "bool",
|
||||||
options: "[dropdownOption]",
|
options: "[$__dropdownOption__$]",
|
||||||
selectedValues: "[string]",
|
selectedValues: "[string]",
|
||||||
},
|
},
|
||||||
STATBOX_WIDGET: {
|
STATBOX_WIDGET: {
|
||||||
|
|
@ -515,7 +536,13 @@ export const entityDefinitions = {
|
||||||
"Map Chart widget shows the graphical representation of your data on the map.",
|
"Map Chart widget shows the graphical representation of your data on the map.",
|
||||||
"!url": "https://docs.appsmith.com/widget-reference/map-chart",
|
"!url": "https://docs.appsmith.com/widget-reference/map-chart",
|
||||||
isVisible: isVisible,
|
isVisible: isVisible,
|
||||||
selectedDataPoint: "mapChartDataPoint",
|
selectedDataPoint: {
|
||||||
|
id: "string",
|
||||||
|
label: "string",
|
||||||
|
originalId: "string",
|
||||||
|
shortLabel: "string",
|
||||||
|
value: "number",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
INPUT_WIDGET_V2: {
|
INPUT_WIDGET_V2: {
|
||||||
"!doc":
|
"!doc":
|
||||||
|
|
@ -607,46 +634,31 @@ export const entityDefinitions = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
$__name__$ is just to reduce occurrences of global def showing up in auto completion for user as `$` is less commonly used as entityName/
|
||||||
|
|
||||||
|
GLOBAL_DEFS are maintained to support definition for array of objects which currently aren't supported by our generateTypeDef.
|
||||||
|
*/
|
||||||
export const GLOBAL_DEFS = {
|
export const GLOBAL_DEFS = {
|
||||||
dropdownOption: {
|
$__dropdownOption__$: {
|
||||||
label: "string",
|
label: "string",
|
||||||
value: "string",
|
value: "string",
|
||||||
},
|
},
|
||||||
tabs: {
|
$__chartDataPoint__$: {
|
||||||
id: "string",
|
|
||||||
label: "string",
|
|
||||||
},
|
|
||||||
chartDataPoint: {
|
|
||||||
x: "string",
|
x: "string",
|
||||||
y: "string",
|
y: "string",
|
||||||
},
|
},
|
||||||
chartData: {
|
$__file__$: {
|
||||||
seriesName: "string",
|
|
||||||
data: "[chartDataPoint]",
|
|
||||||
},
|
|
||||||
latLong: {
|
|
||||||
lat: "number",
|
|
||||||
long: "number",
|
|
||||||
title: "string",
|
|
||||||
},
|
|
||||||
mapMarker: {
|
|
||||||
lat: "number",
|
|
||||||
long: "number",
|
|
||||||
title: "string",
|
|
||||||
description: "string",
|
|
||||||
},
|
|
||||||
file: {
|
|
||||||
data: "string",
|
data: "string",
|
||||||
dataFormat: "string",
|
dataFormat: "string",
|
||||||
name: "text",
|
name: "text",
|
||||||
type: "file",
|
type: "file",
|
||||||
},
|
},
|
||||||
mapChartDataPoint: {
|
$__mapMarker__$: {
|
||||||
id: "string",
|
lat: "number",
|
||||||
label: "string",
|
long: "number",
|
||||||
originalId: "string",
|
title: "string",
|
||||||
shortLabel: "string",
|
description: "string",
|
||||||
value: "number",
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ describe("Tern server", () => {
|
||||||
getCursor: () => ({ ch: 0, line: 0 }),
|
getCursor: () => ({ ch: 0, line: 0 }),
|
||||||
getLine: () => "{{Api.}}",
|
getLine: () => "{{Api.}}",
|
||||||
somethingSelected: () => false,
|
somethingSelected: () => false,
|
||||||
|
getValue: () => "",
|
||||||
} as unknown) as CodeMirror.Doc,
|
} as unknown) as CodeMirror.Doc,
|
||||||
changed: null,
|
changed: null,
|
||||||
},
|
},
|
||||||
|
|
@ -71,9 +72,10 @@ describe("Tern server", () => {
|
||||||
input: {
|
input: {
|
||||||
name: "test",
|
name: "test",
|
||||||
doc: ({
|
doc: ({
|
||||||
getCursor: () => ({ ch: 0, line: 1 }),
|
getCursor: () => ({ ch: 0, line: 0 }),
|
||||||
getLine: () => "{{Api.}}",
|
getLine: () => "{{Api.}}",
|
||||||
somethingSelected: () => false,
|
somethingSelected: () => false,
|
||||||
|
getValue: () => "",
|
||||||
} as unknown) as CodeMirror.Doc,
|
} as unknown) as CodeMirror.Doc,
|
||||||
changed: null,
|
changed: null,
|
||||||
},
|
},
|
||||||
|
|
@ -83,13 +85,14 @@ describe("Tern server", () => {
|
||||||
input: {
|
input: {
|
||||||
name: "test",
|
name: "test",
|
||||||
doc: ({
|
doc: ({
|
||||||
getCursor: () => ({ ch: 3, line: 1 }),
|
getCursor: () => ({ ch: 3, line: 0 }),
|
||||||
getLine: () => "g {{Api.}}",
|
getLine: () => "g {{Api.}}",
|
||||||
somethingSelected: () => false,
|
somethingSelected: () => false,
|
||||||
|
getValue: () => "",
|
||||||
} as unknown) as CodeMirror.Doc,
|
} as unknown) as CodeMirror.Doc,
|
||||||
changed: null,
|
changed: null,
|
||||||
},
|
},
|
||||||
expectedOutput: { ch: 1, line: 0 },
|
expectedOutput: { ch: 3, line: 0 },
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -126,20 +129,20 @@ describe("Tern server", () => {
|
||||||
input: {
|
input: {
|
||||||
codeEditor: {
|
codeEditor: {
|
||||||
value: "\n {{}}",
|
value: "\n {{}}",
|
||||||
cursor: { ch: 3, line: 1 },
|
cursor: { ch: 3, line: 0 },
|
||||||
doc: ({
|
doc: ({
|
||||||
getCursor: () => ({ ch: 3, line: 1 }),
|
getCursor: () => ({ ch: 3, line: 0 }),
|
||||||
getLine: () => " {{}}",
|
getLine: () => " {{}}",
|
||||||
somethingSelected: () => false,
|
somethingSelected: () => false,
|
||||||
} as unknown) as CodeMirror.Doc,
|
} as unknown) as CodeMirror.Doc,
|
||||||
},
|
},
|
||||||
requestCallbackData: {
|
requestCallbackData: {
|
||||||
completions: [{ name: "Api1" }],
|
completions: [{ name: "Api1" }],
|
||||||
start: { ch: 2, line: 1 },
|
start: { ch: 2, line: 0 },
|
||||||
end: { ch: 6, line: 1 },
|
end: { ch: 6, line: 0 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedOutput: { ch: 3, line: 1 },
|
expectedOutput: { ch: 3, line: 0 },
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,26 @@ type ArgHints = {
|
||||||
doc: CodeMirror.Doc;
|
doc: CodeMirror.Doc;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type RequestQuery = {
|
||||||
|
type: string;
|
||||||
|
types?: boolean;
|
||||||
|
docs?: boolean;
|
||||||
|
urls?: boolean;
|
||||||
|
origins?: boolean;
|
||||||
|
caseInsensitive?: boolean;
|
||||||
|
preferFunction?: boolean;
|
||||||
|
end?: CodeMirror.Position;
|
||||||
|
guess?: boolean;
|
||||||
|
inLiteral?: boolean;
|
||||||
|
fullDocs?: any;
|
||||||
|
lineCharPositions?: any;
|
||||||
|
start?: any;
|
||||||
|
file?: any;
|
||||||
|
includeKeywords?: boolean;
|
||||||
|
depth?: number;
|
||||||
|
sort?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export type DataTreeDefEntityInformation = {
|
export type DataTreeDefEntityInformation = {
|
||||||
type: ENTITY_TYPE;
|
type: ENTITY_TYPE;
|
||||||
subType: string;
|
subType: string;
|
||||||
|
|
@ -341,23 +361,13 @@ class TernServer {
|
||||||
|
|
||||||
request(
|
request(
|
||||||
cm: CodeMirror.Editor,
|
cm: CodeMirror.Editor,
|
||||||
query: {
|
query: RequestQuery | string,
|
||||||
type: string;
|
|
||||||
types?: boolean;
|
|
||||||
docs?: boolean;
|
|
||||||
urls?: boolean;
|
|
||||||
origins?: boolean;
|
|
||||||
caseInsensitive?: boolean;
|
|
||||||
preferFunction?: boolean;
|
|
||||||
end?: CodeMirror.Position;
|
|
||||||
guess?: boolean;
|
|
||||||
inLiteral?: boolean;
|
|
||||||
},
|
|
||||||
callbackFn: (error: any, data: any) => void,
|
callbackFn: (error: any, data: any) => void,
|
||||||
pos?: CodeMirror.Position,
|
pos?: CodeMirror.Position,
|
||||||
) {
|
) {
|
||||||
const doc = this.findDoc(cm.getDoc());
|
const doc = this.findDoc(cm.getDoc());
|
||||||
const request = this.buildRequest(doc, query, pos);
|
const request = this.buildRequest(doc, query, pos);
|
||||||
|
|
||||||
// @ts-expect-error: Types are not available
|
// @ts-expect-error: Types are not available
|
||||||
this.server.request(request, callbackFn);
|
this.server.request(request, callbackFn);
|
||||||
}
|
}
|
||||||
|
|
@ -389,55 +399,28 @@ class TernServer {
|
||||||
|
|
||||||
buildRequest(
|
buildRequest(
|
||||||
doc: TernDoc,
|
doc: TernDoc,
|
||||||
query: {
|
query: Partial<RequestQuery> | string,
|
||||||
type?: string;
|
|
||||||
types?: boolean;
|
|
||||||
docs?: boolean;
|
|
||||||
urls?: boolean;
|
|
||||||
origins?: boolean;
|
|
||||||
fullDocs?: any;
|
|
||||||
lineCharPositions?: any;
|
|
||||||
end?: any;
|
|
||||||
start?: any;
|
|
||||||
file?: any;
|
|
||||||
includeKeywords?: boolean;
|
|
||||||
inLiteral?: boolean;
|
|
||||||
depth?: number;
|
|
||||||
sort?: boolean;
|
|
||||||
},
|
|
||||||
pos?: CodeMirror.Position,
|
pos?: CodeMirror.Position,
|
||||||
) {
|
) {
|
||||||
const files = [];
|
const files = [];
|
||||||
let offsetLines = 0;
|
let offsetLines = 0;
|
||||||
|
if (typeof query == "string") query = { type: query };
|
||||||
const allowFragments = !query.fullDocs;
|
const allowFragments = !query.fullDocs;
|
||||||
if (!allowFragments) delete query.fullDocs;
|
if (!allowFragments) delete query.fullDocs;
|
||||||
query.lineCharPositions = true;
|
query.lineCharPositions = true;
|
||||||
query.includeKeywords = true;
|
query.includeKeywords = true;
|
||||||
query.depth = 0;
|
query.depth = 0;
|
||||||
query.sort = true;
|
query.sort = true;
|
||||||
if (!query.end) {
|
if (query.end == null) {
|
||||||
const lineValue = this.lineValue(doc);
|
query.end = pos || doc.doc.getCursor("end");
|
||||||
const focusedValue = this.getFocusedDynamicValue(doc);
|
if (doc.doc.somethingSelected()) query.start = doc.doc.getCursor("start");
|
||||||
const index = lineValue.indexOf(focusedValue);
|
|
||||||
|
|
||||||
const positions = pos || doc.doc.getCursor("end");
|
|
||||||
const queryChPosition = positions.ch - index;
|
|
||||||
|
|
||||||
query.end = {
|
|
||||||
...positions,
|
|
||||||
line: 0,
|
|
||||||
ch: queryChPosition,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (doc.doc.somethingSelected()) {
|
|
||||||
query.start = doc.doc.getCursor("start");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const startPos = query.start || query.end;
|
const startPos = query.start || query.end;
|
||||||
|
|
||||||
if (doc.changed) {
|
if (doc.changed) {
|
||||||
if (
|
if (
|
||||||
doc.doc.lineCount() > bigDoc &&
|
doc.doc.lineCount() > bigDoc &&
|
||||||
allowFragments &&
|
allowFragments !== false &&
|
||||||
doc.changed.to - doc.changed.from < 100 &&
|
doc.changed.to - doc.changed.from < 100 &&
|
||||||
doc.changed.from <= startPos.line &&
|
doc.changed.from <= startPos.line &&
|
||||||
doc.changed.to > query.end.line
|
doc.changed.to > query.end.line
|
||||||
|
|
@ -445,29 +428,36 @@ class TernServer {
|
||||||
files.push(this.getFragmentAround(doc, startPos, query.end));
|
files.push(this.getFragmentAround(doc, startPos, query.end));
|
||||||
query.file = "#0";
|
query.file = "#0";
|
||||||
offsetLines = files[0].offsetLines;
|
offsetLines = files[0].offsetLines;
|
||||||
if (query.start) {
|
if (query.start != null)
|
||||||
query.start = Pos(query.start.line - -offsetLines, query.start.ch);
|
query.start = Pos(query.start.line - -offsetLines, query.start.ch);
|
||||||
}
|
|
||||||
query.end = Pos(query.end.line - offsetLines, query.end.ch);
|
query.end = Pos(query.end.line - offsetLines, query.end.ch);
|
||||||
} else {
|
} else {
|
||||||
files.push({
|
files.push({
|
||||||
type: "full",
|
type: "full",
|
||||||
name: doc.name,
|
name: doc.name,
|
||||||
text: this.getFocusedDynamicValue(doc),
|
text: this.docValue(doc),
|
||||||
});
|
});
|
||||||
query.file = doc.name;
|
query.file = doc.name;
|
||||||
doc.changed = null;
|
doc.changed = null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
query.file = doc.name;
|
query.file = doc.name;
|
||||||
|
// this code is different from tern.js code
|
||||||
|
// we noticed error `TernError: file doesn't contain line x`
|
||||||
|
// which was due to file not being present for the case when a codeEditor is opened and 1st character is typed
|
||||||
|
files.push({
|
||||||
|
type: "full",
|
||||||
|
name: doc.name,
|
||||||
|
text: this.docValue(doc),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
for (const name in this.docs) {
|
for (const name in this.docs) {
|
||||||
const cur = this.docs[name];
|
const cur = this.docs[name];
|
||||||
if (cur.changed && cur !== doc) {
|
if (cur.changed && (cur != doc || cur.name != doc.name)) {
|
||||||
files.push({
|
files.push({
|
||||||
type: "full",
|
type: "full",
|
||||||
name: cur.name,
|
name: cur.name,
|
||||||
text: this.getFocusedDynamicValue(cur),
|
text: this.docValue(cur),
|
||||||
});
|
});
|
||||||
cur.changed = null;
|
cur.changed = null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Def } from "tern";
|
||||||
import { TruthyPrimitiveTypes } from "utils/TypeHelpers";
|
import { TruthyPrimitiveTypes } from "utils/TypeHelpers";
|
||||||
import { generateTypeDef } from "./dataTreeTypeDefCreator";
|
import { generateTypeDef } from "./dataTreeTypeDefCreator";
|
||||||
|
|
||||||
|
|
@ -11,7 +12,7 @@ export type AdditionalDynamicDataTree = Record<
|
||||||
export const customTreeTypeDefCreator = (
|
export const customTreeTypeDefCreator = (
|
||||||
dataTree: AdditionalDynamicDataTree,
|
dataTree: AdditionalDynamicDataTree,
|
||||||
) => {
|
) => {
|
||||||
const def: any = {
|
const def: Def = {
|
||||||
"!name": "customDataTree",
|
"!name": "customDataTree",
|
||||||
};
|
};
|
||||||
Object.keys(dataTree).forEach((entityName) => {
|
Object.keys(dataTree).forEach((entityName) => {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import {
|
||||||
generateTypeDef,
|
generateTypeDef,
|
||||||
dataTreeTypeDefCreator,
|
dataTreeTypeDefCreator,
|
||||||
flattenDef,
|
flattenDef,
|
||||||
|
getFunctionsArgsType,
|
||||||
} from "utils/autocomplete/dataTreeTypeDefCreator";
|
} from "utils/autocomplete/dataTreeTypeDefCreator";
|
||||||
import {
|
import {
|
||||||
DataTreeWidget,
|
DataTreeWidget,
|
||||||
|
|
@ -121,3 +122,49 @@ describe("dataTreeTypeDefCreator", () => {
|
||||||
expect(value).toStrictEqual(expected);
|
expect(value).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("getFunctionsArgsType", () => {
|
||||||
|
const testCases = {
|
||||||
|
testCase1: {
|
||||||
|
arguments: [
|
||||||
|
{ name: "a", value: undefined },
|
||||||
|
{ name: "b", value: undefined },
|
||||||
|
{ name: "c", value: undefined },
|
||||||
|
{ name: "d", value: undefined },
|
||||||
|
{ name: "", value: undefined },
|
||||||
|
],
|
||||||
|
expectedOutput: "fn(a: ?, b: ?, c: ?, d: ?)",
|
||||||
|
},
|
||||||
|
testCase2: {
|
||||||
|
arguments: [],
|
||||||
|
expectedOutput: "fn()",
|
||||||
|
},
|
||||||
|
testCase3: {
|
||||||
|
arguments: [
|
||||||
|
{ name: "a", value: undefined },
|
||||||
|
{ name: "b", value: undefined },
|
||||||
|
{ name: "", value: undefined },
|
||||||
|
{ name: "", value: undefined },
|
||||||
|
],
|
||||||
|
expectedOutput: "fn(a: ?, b: ?)",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
it("function with 4 args", () => {
|
||||||
|
expect(getFunctionsArgsType(testCases.testCase1.arguments)).toEqual(
|
||||||
|
testCases.testCase1.expectedOutput,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("function with no args", () => {
|
||||||
|
expect(getFunctionsArgsType(testCases.testCase2.arguments)).toEqual(
|
||||||
|
testCases.testCase2.expectedOutput,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("function with 2 args", () => {
|
||||||
|
expect(getFunctionsArgsType(testCases.testCase3.arguments)).toEqual(
|
||||||
|
testCases.testCase3.expectedOutput,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,5 @@
|
||||||
import {
|
import { DataTree, ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
|
||||||
DataTree,
|
import { get, isFunction } from "lodash";
|
||||||
ENTITY_TYPE,
|
|
||||||
MetaArgs,
|
|
||||||
} from "entities/DataTree/dataTreeFactory";
|
|
||||||
import _ from "lodash";
|
|
||||||
import { entityDefinitions } from "utils/autocomplete/EntityDefinitions";
|
import { entityDefinitions } from "utils/autocomplete/EntityDefinitions";
|
||||||
import { getType, Types } from "utils/TypeHelpers";
|
import { getType, Types } from "utils/TypeHelpers";
|
||||||
import { Def } from "tern";
|
import { Def } from "tern";
|
||||||
|
|
@ -15,9 +11,11 @@ import {
|
||||||
isWidget,
|
isWidget,
|
||||||
} from "workers/evaluationUtils";
|
} from "workers/evaluationUtils";
|
||||||
import { DataTreeDefEntityInformation } from "utils/autocomplete/TernServer";
|
import { DataTreeDefEntityInformation } from "utils/autocomplete/TernServer";
|
||||||
|
import { Variable } from "entities/JSCollection";
|
||||||
|
|
||||||
// When there is a complex data type, we store it in extra def and refer to it
|
// When there is a complex data type, we store it in extra def and refer to it
|
||||||
// in the def
|
// in the def
|
||||||
let extraDefs: any = {};
|
let extraDefs: Def = {};
|
||||||
// Def names are encoded with information about the entity
|
// Def names are encoded with information about the entity
|
||||||
// This so that we have more info about them
|
// This so that we have more info about them
|
||||||
// when sorting results in autocomplete
|
// when sorting results in autocomplete
|
||||||
|
|
@ -28,16 +26,17 @@ export const dataTreeTypeDefCreator = (
|
||||||
dataTree: DataTree,
|
dataTree: DataTree,
|
||||||
isJSEditorEnabled: boolean,
|
isJSEditorEnabled: boolean,
|
||||||
): { def: Def; entityInfo: Map<string, DataTreeDefEntityInformation> } => {
|
): { def: Def; entityInfo: Map<string, DataTreeDefEntityInformation> } => {
|
||||||
const def: any = {
|
const def: Def = {
|
||||||
"!name": "DATA_TREE",
|
"!name": "DATA_TREE",
|
||||||
};
|
};
|
||||||
const entityMap: Map<string, DataTreeDefEntityInformation> = new Map();
|
const entityMap: Map<string, DataTreeDefEntityInformation> = new Map();
|
||||||
|
|
||||||
Object.entries(dataTree).forEach(([entityName, entity]) => {
|
Object.entries(dataTree).forEach(([entityName, entity]) => {
|
||||||
if (isWidget(entity)) {
|
if (isWidget(entity)) {
|
||||||
const widgetType = entity.type;
|
const widgetType = entity.type;
|
||||||
if (widgetType in entityDefinitions) {
|
if (widgetType in entityDefinitions) {
|
||||||
const definition = _.get(entityDefinitions, widgetType);
|
const definition = get(entityDefinitions, widgetType);
|
||||||
if (_.isFunction(definition)) {
|
if (isFunction(definition)) {
|
||||||
def[entityName] = definition(entity);
|
def[entityName] = definition(entity);
|
||||||
} else {
|
} else {
|
||||||
def[entityName] = definition;
|
def[entityName] = definition;
|
||||||
|
|
@ -49,7 +48,7 @@ export const dataTreeTypeDefCreator = (
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (isAction(entity)) {
|
} else if (isAction(entity)) {
|
||||||
def[entityName] = (entityDefinitions.ACTION as any)(entity);
|
def[entityName] = entityDefinitions.ACTION(entity);
|
||||||
flattenDef(def, entityName);
|
flattenDef(def, entityName);
|
||||||
entityMap.set(entityName, {
|
entityMap.set(entityName, {
|
||||||
type: ENTITY_TYPE.ACTION,
|
type: ENTITY_TYPE.ACTION,
|
||||||
|
|
@ -62,20 +61,27 @@ export const dataTreeTypeDefCreator = (
|
||||||
subType: ENTITY_TYPE.APPSMITH,
|
subType: ENTITY_TYPE.APPSMITH,
|
||||||
});
|
});
|
||||||
} else if (isJSAction(entity) && isJSEditorEnabled) {
|
} else if (isJSAction(entity) && isJSEditorEnabled) {
|
||||||
const metaObj: Record<string, MetaArgs> = entity.meta;
|
const metaObj = entity.meta;
|
||||||
const jsOptions: Record<string, unknown> = {};
|
const jsProperty: Def = {};
|
||||||
|
|
||||||
for (const key in metaObj) {
|
for (const key in metaObj) {
|
||||||
jsOptions[key] =
|
// const jsFunctionObj = metaObj[key];
|
||||||
"fn(onSuccess: fn() -> void, onError: fn() -> void) -> void";
|
// const { arguments: args } = jsFunctionObj;
|
||||||
|
// const argsTypeString = getFunctionsArgsType(args);
|
||||||
|
// As we don't show args we avoid to get args def of function
|
||||||
|
// we will also need to check performance implications here
|
||||||
|
|
||||||
|
const argsTypeString = getFunctionsArgsType([]);
|
||||||
|
jsProperty[key] = argsTypeString;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < entity.variables.length; i++) {
|
for (let i = 0; i < entity.variables.length; i++) {
|
||||||
const varKey = entity.variables[i];
|
const varKey = entity.variables[i];
|
||||||
const varValue = entity[varKey];
|
const varValue = entity[varKey];
|
||||||
jsOptions[varKey] = generateTypeDef(varValue);
|
jsProperty[varKey] = generateTypeDef(varValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
def[entityName] = jsOptions;
|
def[entityName] = jsProperty;
|
||||||
flattenDef(def, entityName);
|
flattenDef(def, entityName);
|
||||||
entityMap.set(entityName, {
|
entityMap.set(entityName, {
|
||||||
type: ENTITY_TYPE.JSACTION,
|
type: ENTITY_TYPE.JSACTION,
|
||||||
|
|
@ -87,12 +93,11 @@ export const dataTreeTypeDefCreator = (
|
||||||
extraDefs = {};
|
extraDefs = {};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { def, entityInfo: entityMap };
|
return { def, entityInfo: entityMap };
|
||||||
};
|
};
|
||||||
|
|
||||||
export function generateTypeDef(
|
export function generateTypeDef(obj: any): string | Def {
|
||||||
obj: any,
|
|
||||||
): string | Record<string, string | Record<string, unknown>> {
|
|
||||||
const type = getType(obj);
|
const type = getType(obj);
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case Types.ARRAY: {
|
case Types.ARRAY: {
|
||||||
|
|
@ -100,7 +105,7 @@ export function generateTypeDef(
|
||||||
return `[${arrayType}]`;
|
return `[${arrayType}]`;
|
||||||
}
|
}
|
||||||
case Types.OBJECT: {
|
case Types.OBJECT: {
|
||||||
const objType: Record<string, string | Record<string, unknown>> = {};
|
const objType: Def = {};
|
||||||
Object.keys(obj).forEach((k) => {
|
Object.keys(obj).forEach((k) => {
|
||||||
objType[k] = generateTypeDef(obj[k]);
|
objType[k] = generateTypeDef(obj[k]);
|
||||||
});
|
});
|
||||||
|
|
@ -138,3 +143,32 @@ export const flattenDef = (def: Def, entityName: string): Def => {
|
||||||
}
|
}
|
||||||
return flattenedDef;
|
return flattenedDef;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const VALID_VARIABLE_NAME_REGEX = /^([a-zA-Z_$][a-zA-Z\d_$]*)$/;
|
||||||
|
|
||||||
|
const isValidVariableName = (variableName: string) =>
|
||||||
|
VALID_VARIABLE_NAME_REGEX.test(variableName);
|
||||||
|
|
||||||
|
export const getFunctionsArgsType = (args: Variable[]): string => {
|
||||||
|
// skip same name args to avoiding creating invalid type
|
||||||
|
const argNames = new Set<string>();
|
||||||
|
// skip invalid args name
|
||||||
|
args.forEach((arg) => {
|
||||||
|
if (arg.name && isValidVariableName(arg.name)) argNames.add(arg.name);
|
||||||
|
});
|
||||||
|
const argNamesArray = [...argNames];
|
||||||
|
const argsTypeString = argNamesArray.reduce(
|
||||||
|
(accumulatedArgType, argName, currentIndex) => {
|
||||||
|
switch (currentIndex) {
|
||||||
|
case 0:
|
||||||
|
return `${argName}: ?`;
|
||||||
|
case 1:
|
||||||
|
return `${accumulatedArgType}, ${argName}: ?`;
|
||||||
|
default:
|
||||||
|
return `${accumulatedArgType}, ${argName}: ?`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
argNamesArray[0],
|
||||||
|
);
|
||||||
|
return argsTypeString ? `fn(${argsTypeString})` : `fn()`;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ import {
|
||||||
getUpdatedLocalUnEvalTreeAfterJSUpdates,
|
getUpdatedLocalUnEvalTreeAfterJSUpdates,
|
||||||
parseJSActions,
|
parseJSActions,
|
||||||
} from "workers/JSObject";
|
} from "workers/JSObject";
|
||||||
import { lintTree } from "workers/Lint";
|
import { lintTree } from "workers/Lint/index";
|
||||||
|
|
||||||
export default class DataTreeEvaluator {
|
export default class DataTreeEvaluator {
|
||||||
dependencyMap: DependencyMap = {};
|
dependencyMap: DependencyMap = {};
|
||||||
|
|
@ -808,6 +808,8 @@ export default class DataTreeEvaluator {
|
||||||
entityType = entity.type;
|
entityType = entity.type;
|
||||||
} else if (entity && isAction(entity)) {
|
} else if (entity && isAction(entity)) {
|
||||||
entityType = entity.pluginType;
|
entityType = entity.pluginType;
|
||||||
|
} else if (entity && isJSAction(entity)) {
|
||||||
|
entityType = entity.ENTITY_TYPE;
|
||||||
}
|
}
|
||||||
this.errors.push({
|
this.errors.push({
|
||||||
type: EvalErrorTypes.CYCLICAL_DEPENDENCY_ERROR,
|
type: EvalErrorTypes.CYCLICAL_DEPENDENCY_ERROR,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user