Skip to content
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
0382d29
show lint errors as warnings
vnodecg Jul 14, 2021
1d2678b
Merge branch 'release' into feature/linting-errors
vnodecg Jul 14, 2021
966c446
Merge branch 'release' into feature/linting-errors
vnodecg Jul 20, 2021
733361d
add initial code
vnodecg Jul 27, 2021
6e766ac
Merge remote-tracking branch 'origin/release' into feature-lint-errors
vnodecg Jul 27, 2021
15cf1e2
adjust editor positions
vnodecg Jul 27, 2021
07b8235
update pr comments
vnodecg Jul 27, 2021
7ae6b66
mark jshint errors
vnodecg Jul 29, 2021
4631e60
Merge remote-tracking branch 'origin/release' into FEATURE/linting-er…
vnodecg Jul 29, 2021
d8ec26c
reset changes
vnodecg Jul 30, 2021
81c675c
remove unused prop
vnodecg Jul 30, 2021
c870580
fix test errors
vnodecg Jul 30, 2021
36ccf2c
remove unused imports
vnodecg Jul 31, 2021
8024f66
dont show warning in the error counter
vnodecg Aug 2, 2021
336a59e
show yellow if warning
vnodecg Aug 2, 2021
4609742
remove active error functionality
vnodecg Aug 3, 2021
4b85ec8
update linter to use async functionality
vnodecg Aug 4, 2021
fe79c2e
update linter messages
vnodecg Aug 5, 2021
8983554
update binding positions
vnodecg Aug 10, 2021
106d78d
fix evaluate tests
vnodecg Aug 10, 2021
c6219f1
Merge branch 'release' into feature/Show-lint-errors-in-codeeditor
vnodecg Aug 12, 2021
7c3cec6
Merge branch 'feature/linting-errors' into feature/Show-lint-errors-i…
vnodecg Aug 12, 2021
ca40e4c
dont show undefined errors in debugger
vnodecg Aug 12, 2021
35989b4
move lint code to separate file
vnodecg Aug 12, 2021
afbddef
update testes
vnodecg Aug 12, 2021
a6dde5e
remove unused import
vnodecg Aug 12, 2021
0393726
update tests
vnodecg Aug 13, 2021
97aa7bc
address pr comments
vnodecg Aug 16, 2021
8c89473
add comment to explain why
vnodecg Aug 16, 2021
22c109b
Merge branch 'release' into feature/Show-lint-errors-in-codeeditor
vnodecg Aug 17, 2021
6fe0c95
Merge branch 'release' into feature/Show-lint-errors-in-codeeditor
vnodecg Aug 17, 2021
b82c8e5
replace proper regex
vnodecg Aug 19, 2021
ed9f8fe
fix undefined message error
vnodecg Aug 19, 2021
6c6793a
Update styling for warnings
vnodecg Aug 20, 2021
31d4b06
Merge branch 'release' into feature/Show-lint-errors-in-codeeditor
hetunandu Aug 25, 2021
08085d2
Fix crashing error
hetunandu Aug 25, 2021
d9d125c
Add feature flags
hetunandu Aug 25, 2021
b27aa58
Merge branch 'release' into feature/Show-lint-errors-in-codeeditor
hetunandu Aug 25, 2021
45ce0cf
fix Debugger error counts checks
hetunandu Aug 25, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 46 additions & 3 deletions app/client/src/components/editorComponents/CodeEditor/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import React, { Component } from "react";
import { connect } from "react-redux";
import { AppState } from "reducers";
import CodeMirror, { EditorConfiguration } from "codemirror";
import CodeMirror, {
Annotation,
EditorConfiguration,
UpdateLintingCallback,
} from "codemirror";
import "codemirror/lib/codemirror.css";
import "codemirror/theme/duotone-dark.css";
import "codemirror/theme/duotone-light.css";
Expand All @@ -12,6 +16,9 @@ import "codemirror/addon/edit/closebrackets";
import "codemirror/addon/display/autorefresh";
import "codemirror/addon/mode/multiplex";
import "codemirror/addon/tern/tern.css";
import "codemirror/addon/lint/lint";
import "codemirror/addon/lint/lint.css";

import { getDataTreeForAutocomplete } from "selectors/dataTreeSelectors";
import EvaluatedValuePopup from "components/editorComponents/CodeEditor/EvaluatedValuePopup";
import { WrappedFieldInputProps } from "redux-form";
Expand Down Expand Up @@ -70,6 +77,7 @@ import { getPluginIdToImageLocation } from "sagas/selectors";
import { ExpectedValueExample } from "utils/validation/common";
import { getRecentEntityIds } from "selectors/globalSearchSelectors";
import { AutocompleteDataType } from "utils/autocomplete/TernServer";
import { getLintAnnotations } from "./lintHelpers";

const AUTOCOMPLETE_CLOSE_KEY_CODES = [
"Enter",
Expand Down Expand Up @@ -147,8 +155,9 @@ class CodeEditor extends Component<Props, State> {
codeEditorTarget = React.createRef<HTMLDivElement>();
editor!: CodeMirror.Editor;
hinters: Hinter[] = [];
annotations: Annotation[] = [];
updateLintingCallback: UpdateLintingCallback | undefined;
private editorWrapperRef = React.createRef<HTMLDivElement>();

constructor(props: Props) {
super(props);
this.state = {
Expand All @@ -160,6 +169,28 @@ class CodeEditor extends Component<Props, State> {
this.updatePropertyValue = this.updatePropertyValue.bind(this);
}

lintCode() {
Comment thread
vnodecg marked this conversation as resolved.
Outdated
const { dataTreePath, dynamicData } = this.props;

if (!dataTreePath || !this.updateLintingCallback) {
return;
}

const errors = _.get(
dynamicData,
getEvalErrorPath(dataTreePath),
[],
) as EvaluationError[];

let annotations: Annotation[] = [];

if (this.editor) {
annotations = getLintAnnotations(this.editor.getValue(), errors);
}

this.updateLintingCallback(this.editor, annotations);
}

componentDidMount(): void {
if (this.codeEditorTarget.current) {
const options: EditorConfiguration = {
Expand All @@ -176,6 +207,13 @@ class CodeEditor extends Component<Props, State> {
scrollbarStyle:
this.props.size !== EditorSize.COMPACT ? "native" : "null",
placeholder: this.props.placeholder,
lint: {
getAnnotations: (_: string, callback: UpdateLintingCallback) => {
this.updateLintingCallback = callback;
},
async: true,
lintOnChange: false,
},
};

if (!this.props.input.onChange || this.props.disabled) {
Expand Down Expand Up @@ -313,7 +351,8 @@ class CodeEditor extends Component<Props, State> {
if (!this.props.input.onChange || this.props.disabled) {
return;
}
const mode = cm.getModeAt(cm.getCursor());
const cursor = cm.getCursor();
Comment thread
vnodecg marked this conversation as resolved.
Outdated
const mode = cm.getModeAt(cursor);
if (
mode &&
[EditorModes.JAVASCRIPT, EditorModes.JSON].includes(mode.name)
Expand Down Expand Up @@ -472,9 +511,11 @@ class CodeEditor extends Component<Props, State> {
getEvalErrorPath(dataTreePath),
[],
) as EvaluationError[];

const filteredLintErrors = errors.filter(
(error) => error.errorType !== PropertyEvaluationErrorType.LINT,
);

const pathEvaluatedValue = _.get(dataTree, getEvalValuePath(dataTreePath));

return {
Expand Down Expand Up @@ -515,6 +556,8 @@ class CodeEditor extends Component<Props, State> {
evaluated = pathEvaluatedValue;
}

this.lintCode();

const showEvaluatedValue =
this.state.isFocused &&
("evaluatedValue" in this.props ||
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { getKeyPositionInString, getAllOccurences } from "./lintHelpers";

describe("getAllOccurences()", function() {
it("should get all the indexes", () => {
const res = getAllOccurences("this is a `this` string", "this");
expect(res).toEqual([0, 11]);
});

it("should return empty array", () => {
expect(getAllOccurences("this is a string", "number")).toEqual([]);
});
});

describe("getKeyPositionsInString()", () => {
it("should return results for single line string", () => {
const res = getKeyPositionInString("this is a `this` string", "this");
expect(res).toEqual([
{ line: 0, ch: 0 },
{ line: 0, ch: 11 },
]);
});

it("should return results for multiline string", () => {
const res = getKeyPositionInString("this is a \n`this` string", "this");
expect(res).toEqual([
{ line: 0, ch: 0 },
{ line: 1, ch: 1 },
]);
});
});
103 changes: 103 additions & 0 deletions app/client/src/components/editorComponents/CodeEditor/lintHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import _ from "lodash";
import { Annotation, Position } from "codemirror";
import {
EvaluationError,
PropertyEvaluationErrorType,
} from "utils/DynamicBindingUtils";

export const getAllOccurences = (str: string, key: string) => {
const indicies = [];
const keylen = key.length;
let index, startIndex;
index = str.indexOf(key, startIndex);
while (index > -1) {
indicies.push(index);
startIndex = index + keylen;
index = str.indexOf(key, startIndex);
}

return indicies;
};

export const getKeyPositionInString = (
str: string,
key: string,
): Position[] => {
const indicies = getAllOccurences(str, key);
let positions: Position[] = [];
if (str.includes("\n")) {
for (const index of indicies) {
const substr = str.substr(0, index);
const substrLines = substr.split("\n");
const ch = _.last(substrLines)?.length || 0;
const line = substrLines.length - 1;

positions.push({ line, ch });
}
} else {
positions = indicies.map((index) => ({ line: 0, ch: index }));
}
return positions;
};

export const getLintAnnotations = (
value: string,
errors: EvaluationError[],
): Annotation[] => {
const annotations: Annotation[] = [];
const lintErrors = errors.filter(
(error) => error.errorType === PropertyEvaluationErrorType.LINT,
);

lintErrors.forEach((error) => {
const { errorMessage, originalBinding, severity, variables } = error;

if (!originalBinding) {
return annotations;
}

// We find the location of binding in the editor value and then
// we find locations of jshint variabls (a, b, c, d) in the binding and highlight them
const bindingPositions = getKeyPositionInString(value, originalBinding);

for (const bindingLocation of bindingPositions) {
if (variables) {
for (const variable of variables) {
if (variable && originalBinding.includes(variable)) {
const variableLocations = getKeyPositionInString(
originalBinding,
variable,
);

for (const variableLocation of variableLocations) {
const from = {
line: bindingLocation.line + variableLocation.line,
// if the binding is a multiline function we need to
// use jshint variable position as the starting point
ch:
variableLocation.line > 0
? variableLocation.ch
: variableLocation.ch + bindingLocation.ch,
};

const to = {
line: from.line,
ch: from.ch + variable.length,
};

const annotation = {
from,
to,
message: errorMessage,
severity: severity,
};

annotations.push(annotation);
}
}
}
}
}
});
return annotations;
};
28 changes: 19 additions & 9 deletions app/client/src/components/editorComponents/Debugger/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { getTypographyByKey } from "constants/DefaultTheme";
import { Layers } from "constants/Layers";
import { stopEventPropagation } from "utils/AppsmithUtils";

const Container = styled.div<{ errorCount: number }>`
const Container = styled.div<{ errorCount: number; warningCount: number }>`
z-index: ${Layers.debugger};
background-color: ${(props) =>
props.theme.colors.debugger.floatingButton.background};
Expand All @@ -40,8 +40,10 @@ const Container = styled.div<{ errorCount: number }>`
height: 16px;
padding: ${(props) => props.theme.spaces[1]}px;
background-color: ${(props) =>
!!props.errorCount
? props.theme.colors.debugger.floatingButton.errorCount
props.errorCount + props.warningCount > 0
? props.errorCount === 0
? props.theme.colors.debugger.floatingButton.warningCount
: props.theme.colors.debugger.floatingButton.errorCount
: props.theme.colors.debugger.floatingButton.noErrorCount};
border-radius: 10px;
position: absolute;
Expand All @@ -55,9 +57,16 @@ const Container = styled.div<{ errorCount: number }>`

function Debugger() {
const dispatch = useDispatch();
const errorCount = useSelector(
(state: AppState) => Object.keys(state.ui.debugger.errors).length,
);
const messageCounters = useSelector((state) => {
const errorKeys = Object.keys(state.ui.debugger.errors);
const warnings = errorKeys.filter((key: string) => key.includes("warning"))
.length;
const errors = errorKeys.length - warnings;
return { errors, warnings };
});

const totalMessageCount = messageCounters.errors + messageCounters.warnings;

const showDebugger = useSelector(
(state: AppState) => state.ui.debugger.isOpen,
);
Expand All @@ -74,14 +83,15 @@ function Debugger() {
return (
<Container
className="t--debugger"
errorCount={errorCount}
errorCount={messageCounters.errors}
onClick={onClick}
warningCount={messageCounters.warnings}
>
<Icon name="bug" size={IconSize.XL} />
<div className="debugger-count">{errorCount}</div>
<div className="debugger-count">{totalMessageCount}</div>
</Container>
);
return <DebuggerTabs defaultIndex={errorCount ? 0 : 1} />;
return <DebuggerTabs defaultIndex={totalMessageCount ? 0 : 1} />;
}

export default Debugger;
3 changes: 3 additions & 0 deletions app/client/src/constants/DefaultTheme.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1191,6 +1191,7 @@ type ColorType = {
shadow: string;
errorCount: string;
noErrorCount: string;
warningCount: string;
};
blankState: {
shortcut: string;
Expand Down Expand Up @@ -1935,6 +1936,7 @@ export const dark: ColorType = {
shadow: "0px 12px 28px -6px rgba(0, 0, 0, 0.32)",
errorCount: "#F22B2B",
noErrorCount: "#03B365",
warningCount: "#DCAD00",
},
inspectElement: {
color: "#D4D4D4",
Expand Down Expand Up @@ -2499,6 +2501,7 @@ export const light: ColorType = {
shadow: "0px 12px 28px -6px rgba(0, 0, 0, 0.32)",
errorCount: "#F22B2B",
noErrorCount: "#03B365",
warningCount: "#DCAD00",
},
inspectElement: {
color: "#090707",
Expand Down
1 change: 1 addition & 0 deletions app/client/src/entities/AppsmithConsole/logtype.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ enum LOG_TYPE {
ACTION_EXECUTION_SUCCESS,
ENTITY_DELETED,
EVAL_ERROR,
EVAL_WARNING,
ACTION_UPDATE,
}

Expand Down
15 changes: 14 additions & 1 deletion app/client/src/globalStyles/CodmirrorHintStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,19 @@ export const CodemirrorHintStyles = createGlobalStyle<{
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.12) !important;
overflow: scroll;
}

}
.CodeMirror-lint-tooltip {
border: none;
background: ${(props) =>
props.editorTheme === EditorTheme.DARK ? "#23292e" : "#fff"};
box-shadow: 0px 12px 28px -6px rgba(0, 0, 0, 0.32);
padding: 7px 12px;
border-radius: 0;
}
.CodeMirror-lint-message {
margin-top: 5px;
margin-bottom: 5px;
font-family: ${(props) => props.theme.fonts.text};
color: #4B4848;
}
`;
Loading