Skip to content

Commit cb0d22b

Browse files
authored
Merge branch 'release' into feat/add_multiplayer_feature_flag
2 parents d2519c3 + 9f3a24f commit cb0d22b

File tree

22 files changed

+796
-80
lines changed

22 files changed

+796
-80
lines changed

app/client/src/components/editorComponents/CodeEditor/index.tsx

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import React, { Component } from "react";
22
import { connect } from "react-redux";
33
import { AppState } from "reducers";
4-
import CodeMirror, { EditorConfiguration } from "codemirror";
4+
import CodeMirror, {
5+
Annotation,
6+
EditorConfiguration,
7+
UpdateLintingCallback,
8+
} from "codemirror";
59
import "codemirror/lib/codemirror.css";
610
import "codemirror/theme/duotone-dark.css";
711
import "codemirror/theme/duotone-light.css";
@@ -12,6 +16,9 @@ import "codemirror/addon/edit/closebrackets";
1216
import "codemirror/addon/display/autorefresh";
1317
import "codemirror/addon/mode/multiplex";
1418
import "codemirror/addon/tern/tern.css";
19+
import "codemirror/addon/lint/lint";
20+
import "codemirror/addon/lint/lint.css";
21+
1522
import { getDataTreeForAutocomplete } from "selectors/dataTreeSelectors";
1623
import EvaluatedValuePopup from "components/editorComponents/CodeEditor/EvaluatedValuePopup";
1724
import { WrappedFieldInputProps } from "redux-form";
@@ -70,6 +77,8 @@ import { getPluginIdToImageLocation } from "sagas/selectors";
7077
import { ExpectedValueExample } from "utils/validation/common";
7178
import { getRecentEntityIds } from "selectors/globalSearchSelectors";
7279
import { AutocompleteDataType } from "utils/autocomplete/TernServer";
80+
import { getLintAnnotations } from "./lintHelpers";
81+
import getFeatureFlags from "utils/featureFlags";
7382

7483
const AUTOCOMPLETE_CLOSE_KEY_CODES = [
7584
"Enter",
@@ -147,8 +156,9 @@ class CodeEditor extends Component<Props, State> {
147156
codeEditorTarget = React.createRef<HTMLDivElement>();
148157
editor!: CodeMirror.Editor;
149158
hinters: Hinter[] = [];
159+
annotations: Annotation[] = [];
160+
updateLintingCallback: UpdateLintingCallback | undefined;
150161
private editorWrapperRef = React.createRef<HTMLDivElement>();
151-
152162
constructor(props: Props) {
153163
super(props);
154164
this.state = {
@@ -176,6 +186,13 @@ class CodeEditor extends Component<Props, State> {
176186
scrollbarStyle:
177187
this.props.size !== EditorSize.COMPACT ? "native" : "null",
178188
placeholder: this.props.placeholder,
189+
lint: {
190+
getAnnotations: (_: string, callback: UpdateLintingCallback) => {
191+
this.updateLintingCallback = callback;
192+
},
193+
async: true,
194+
lintOnChange: false,
195+
},
179196
};
180197

181198
if (!this.props.input.onChange || this.props.disabled) {
@@ -441,6 +458,28 @@ class CodeEditor extends Component<Props, State> {
441458
}
442459
};
443460

461+
lintCode() {
462+
const { dataTreePath, dynamicData } = this.props;
463+
464+
if (!dataTreePath || !this.updateLintingCallback) {
465+
return;
466+
}
467+
468+
const errors = _.get(
469+
dynamicData,
470+
getEvalErrorPath(dataTreePath),
471+
[],
472+
) as EvaluationError[];
473+
474+
let annotations: Annotation[] = [];
475+
476+
if (this.editor) {
477+
annotations = getLintAnnotations(this.editor.getValue(), errors);
478+
}
479+
480+
this.updateLintingCallback(this.editor, annotations);
481+
}
482+
444483
static updateMarkings = (
445484
editor: CodeMirror.Editor,
446485
marking: Array<MarkHelper>,
@@ -488,9 +527,11 @@ class CodeEditor extends Component<Props, State> {
488527
getEvalErrorPath(dataTreePath),
489528
[],
490529
) as EvaluationError[];
530+
491531
const filteredLintErrors = errors.filter(
492532
(error) => error.errorType !== PropertyEvaluationErrorType.LINT,
493533
);
534+
494535
const pathEvaluatedValue = _.get(dataTree, getEvalValuePath(dataTreePath));
495536

496537
return {
@@ -531,6 +572,10 @@ class CodeEditor extends Component<Props, State> {
531572
evaluated = pathEvaluatedValue;
532573
}
533574

575+
if (getFeatureFlags().LINTING) {
576+
this.lintCode();
577+
}
578+
534579
const showEvaluatedValue =
535580
this.state.isFocused &&
536581
("evaluatedValue" in this.props ||
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import { Severity } from "entities/AppsmithConsole";
2+
import {
3+
EvaluationError,
4+
PropertyEvaluationErrorType,
5+
} from "utils/DynamicBindingUtils";
6+
import {
7+
getKeyPositionInString,
8+
getLintAnnotations,
9+
getAllWordOccurrences,
10+
} from "./lintHelpers";
11+
12+
describe("getAllWordOccurences()", function() {
13+
it("should get all the indexes", () => {
14+
const res = getAllWordOccurrences("this is a `this` string", "this");
15+
expect(res).toEqual([0, 11]);
16+
});
17+
18+
it("should return empty array", () => {
19+
expect(getAllWordOccurrences("this is a string", "number")).toEqual([]);
20+
});
21+
});
22+
23+
describe("getKeyPositionsInString()", () => {
24+
it("should return results for single line string", () => {
25+
const res = getKeyPositionInString("this is a `this` string", "this");
26+
expect(res).toEqual([
27+
{ line: 0, ch: 0 },
28+
{ line: 0, ch: 11 },
29+
]);
30+
});
31+
32+
it("should return results for multiline string", () => {
33+
const res = getKeyPositionInString("this is a \n`this` string", "this");
34+
expect(res).toEqual([
35+
{ line: 0, ch: 0 },
36+
{ line: 1, ch: 1 },
37+
]);
38+
});
39+
});
40+
41+
describe("getLintAnnotations()", () => {
42+
const { LINT, PARSE } = PropertyEvaluationErrorType;
43+
const { ERROR, WARNING } = Severity;
44+
it("should return proper annotations", () => {
45+
const value = `Hello {{ world == test }}\n {{text}}`;
46+
const errors: EvaluationError[] = [
47+
{
48+
errorMessage: "Expected '===' and instead saw '=='.",
49+
severity: WARNING,
50+
raw:
51+
"\n function closedFunction () {\n const result = world == test \n return result;\n }\n closedFunction()\n ",
52+
errorType: LINT,
53+
originalBinding: "world == test ",
54+
errorSegment: " const result = world == test ",
55+
variables: ["===", "==", null, null],
56+
code: "W116",
57+
},
58+
{
59+
errorType: LINT,
60+
raw:
61+
"\n function closedFunction () {\n const result = world == test \n return result;\n }\n closedFunction()\n ",
62+
severity: WARNING,
63+
errorMessage: "'world' is not defined.",
64+
errorSegment: " const result = world == test ",
65+
originalBinding: "world == test ",
66+
variables: ["world", null, null, null],
67+
code: "W117",
68+
},
69+
{
70+
errorType: LINT,
71+
raw:
72+
"\n function closedFunction () {\n const result = world == test \n return result;\n }\n closedFunction()\n ",
73+
severity: WARNING,
74+
errorMessage: "'test' is not defined.",
75+
errorSegment: " const result = world == test ",
76+
originalBinding: "world == test ",
77+
variables: ["test", null, null, null],
78+
code: "W117",
79+
},
80+
{
81+
errorType: PARSE,
82+
raw:
83+
"\n function closedFunction () {\n const result = world == test \n return result;\n }\n closedFunction()\n ",
84+
severity: ERROR,
85+
errorMessage: "ReferenceError: world is not defined",
86+
originalBinding: " world == test ",
87+
},
88+
{
89+
errorMessage: "'text' is not defined.",
90+
severity: WARNING,
91+
raw:
92+
"\n function closedFunction () {\n const result = text\n return result;\n }\n closedFunction()\n ",
93+
errorType: LINT,
94+
originalBinding: "text",
95+
errorSegment: " const result = text",
96+
variables: ["text", null, null, null],
97+
code: "W117",
98+
},
99+
{
100+
errorMessage: "ReferenceError: text is not defined",
101+
severity: WARNING,
102+
raw:
103+
"\n function closedFunction () {\n const result = text\n return result;\n }\n closedFunction()\n ",
104+
errorType: PARSE,
105+
originalBinding: "text",
106+
},
107+
];
108+
109+
const res = getLintAnnotations(value, errors);
110+
expect(res).toEqual([
111+
{
112+
from: {
113+
line: 0,
114+
ch: 15,
115+
},
116+
to: {
117+
line: 0,
118+
ch: 17,
119+
},
120+
message: "Expected '===' and instead saw '=='.",
121+
severity: "warning",
122+
},
123+
{
124+
from: {
125+
line: 0,
126+
ch: 9,
127+
},
128+
to: {
129+
line: 0,
130+
ch: 14,
131+
},
132+
message: "'world' is not defined.",
133+
severity: "warning",
134+
},
135+
{
136+
from: {
137+
line: 0,
138+
ch: 18,
139+
},
140+
to: {
141+
line: 0,
142+
ch: 22,
143+
},
144+
message: "'test' is not defined.",
145+
severity: "warning",
146+
},
147+
{
148+
from: {
149+
line: 1,
150+
ch: 4,
151+
},
152+
to: {
153+
line: 1,
154+
ch: 8,
155+
},
156+
message: "'text' is not defined.",
157+
severity: "warning",
158+
},
159+
]);
160+
});
161+
});

0 commit comments

Comments
 (0)