## Description Adds a tooltip that shows the function signatures when the cursor is between ( ) to help complete function arguments. https://www.figma.com/file/5QitVVvqgEc6nhR7SbdinY/ADS2.0---Tokens?type=design&node-id=7639-124926&mode=dev #### PR fixes following issue(s) Fixes #4789 > #### Media <img width="483" alt="Screenshot 2023-11-06 at 12 10 42" src="https://github.com/appsmithorg/appsmith/assets/32433245/efbe1aba-9067-4d1e-b9df-72a24f4f8c64"> #### Type of change - New feature (non-breaking change which adds functionality) > > ## Testing > #### How Has This Been Tested? - [x] Manual - [x] Cypress > > #### Test Plan - [x] Function signature tooltip should show when the cursor is in between `( )`. - [x] Tooltip should disappear when the cursor is moved outside '( )`. - [x] Tooltip should disappear when peek over popover is triggered. - [x] Tooltip appear as soon as a function is selected from autocomplete results. - [x] Evaluated Value popover should disappear when tooltip popover is open. #### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) > > ## Checklist: #### Dev activity - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed --------- Co-authored-by: Aishwarya UR <aishwarya@appsmith.com>
146 lines
4.3 KiB
TypeScript
146 lines
4.3 KiB
TypeScript
import { isTrueObject } from "@shared/ast/src/utils";
|
|
import type { WidgetEntityConfig } from "@appsmith/entities/DataTree/types";
|
|
import type { DataTreeEntity } from "entities/DataTree/dataTreeTypes";
|
|
import type { Variable } from "entities/JSCollection";
|
|
import { isObject, uniqueId } from "lodash";
|
|
import type { Def } from "tern";
|
|
import { Types, getType } from "utils/TypeHelpers";
|
|
import { shouldAddSetter } from "workers/Evaluation/evaluate";
|
|
import { typeToTernType } from "workers/common/JSLibrary/ternDefinitionGenerator";
|
|
|
|
export type ExtraDef = Record<string, Def | string>;
|
|
|
|
export const flattenDef = (def: Def, entityName: string): Def => {
|
|
const flattenedDef = def;
|
|
if (!isTrueObject(def[entityName])) return flattenedDef;
|
|
Object.entries(def[entityName]).forEach(([key, value]) => {
|
|
if (key.startsWith("!")) return;
|
|
const keyIsValid = isValidVariableName(key);
|
|
const parentCompletion = !keyIsValid
|
|
? `${entityName}["${key}"]`
|
|
: `${entityName}.${key}`;
|
|
flattenedDef[parentCompletion] = value;
|
|
if (!isTrueObject(value)) return;
|
|
Object.entries(value).forEach(([subKey, subValue]) => {
|
|
if (subKey.startsWith("!")) return;
|
|
const childKeyIsValid = isValidVariableName(subKey);
|
|
const childCompletion = !childKeyIsValid
|
|
? `${parentCompletion}["${subKey}"]`
|
|
: `${parentCompletion}.${subKey}`;
|
|
flattenedDef[childCompletion] = subValue;
|
|
});
|
|
});
|
|
return flattenedDef;
|
|
};
|
|
|
|
export function generateTypeDef(
|
|
value: unknown,
|
|
extraDefsToDefine?: ExtraDef,
|
|
depth = 0,
|
|
): Def | string {
|
|
switch (getType(value)) {
|
|
case Types.ARRAY: {
|
|
const array = value as [unknown];
|
|
if (depth > 5) {
|
|
return `[?]`;
|
|
}
|
|
|
|
const arrayElementType = generateTypeDef(
|
|
array[0],
|
|
extraDefsToDefine,
|
|
depth + 1,
|
|
);
|
|
|
|
if (isObject(arrayElementType)) {
|
|
if (extraDefsToDefine) {
|
|
const uniqueDefName = uniqueId("def_");
|
|
extraDefsToDefine[uniqueDefName] = arrayElementType;
|
|
return `[${uniqueDefName}]`;
|
|
}
|
|
return `[?]`;
|
|
}
|
|
return `[${arrayElementType}]`;
|
|
}
|
|
case Types.OBJECT: {
|
|
const objType: Def = {};
|
|
const object = value as Record<string, unknown>;
|
|
Object.keys(object).forEach((k) => {
|
|
objType[k] = generateTypeDef(object[k], extraDefsToDefine, depth);
|
|
});
|
|
return objType;
|
|
}
|
|
case Types.STRING:
|
|
return "string";
|
|
case Types.NUMBER:
|
|
return "number";
|
|
case Types.BOOLEAN:
|
|
return "bool";
|
|
case Types.NULL:
|
|
case Types.UNDEFINED:
|
|
return "?";
|
|
default:
|
|
return "?";
|
|
}
|
|
}
|
|
|
|
const VALID_VARIABLE_NAME_REGEX = /^([a-zA-Z_$][a-zA-Z\d_$]*)$/;
|
|
|
|
export 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()`;
|
|
};
|
|
|
|
export function generateJSFunctionTypeDef(
|
|
jsData: Record<string, unknown> = {},
|
|
fullFunctionName: string,
|
|
extraDefs: ExtraDef,
|
|
) {
|
|
return {
|
|
"!type": getFunctionsArgsType([]),
|
|
data: generateTypeDef(jsData[fullFunctionName], extraDefs),
|
|
};
|
|
}
|
|
|
|
export function addSettersToDefinitions(
|
|
definitions: Def,
|
|
entity: DataTreeEntity,
|
|
entityConfig?: WidgetEntityConfig,
|
|
) {
|
|
if (entityConfig && entityConfig.__setters) {
|
|
const setters = Object.keys(entityConfig.__setters);
|
|
|
|
setters.forEach((setterName: string) => {
|
|
const setter = entityConfig.__setters?.[setterName];
|
|
const setterType = typeToTernType(
|
|
entityConfig.__setters?.[setterName].type,
|
|
);
|
|
|
|
if (shouldAddSetter(setter, entity)) {
|
|
definitions[setterName] = `fn(value: ${setterType}) -> +Promise`;
|
|
}
|
|
});
|
|
}
|
|
}
|