2022-07-22 20:31:08 +00:00
|
|
|
import { DataTree, DataTreeEntity } from "entities/DataTree/dataTreeFactory";
|
2022-11-03 09:23:15 +00:00
|
|
|
|
2021-10-05 13:52:27 +00:00
|
|
|
import { Position } from "codemirror";
|
|
|
|
|
import {
|
2022-07-22 20:31:08 +00:00
|
|
|
isDynamicValue,
|
|
|
|
|
isPathADynamicBinding,
|
2022-11-03 09:23:15 +00:00
|
|
|
LintError,
|
2021-10-05 13:52:27 +00:00
|
|
|
PropertyEvaluationErrorType,
|
|
|
|
|
} from "utils/DynamicBindingUtils";
|
2022-12-21 17:14:47 +00:00
|
|
|
import { MAIN_THREAD_ACTION } from "workers/Evaluation/evalWorkerActions";
|
2021-10-07 12:33:15 +00:00
|
|
|
import {
|
2022-11-03 09:23:15 +00:00
|
|
|
JSHINT as jshint,
|
|
|
|
|
LintError as JSHintError,
|
|
|
|
|
LintOptions,
|
|
|
|
|
} from "jshint";
|
|
|
|
|
import { get, isEmpty, isNumber, keys, last, set } from "lodash";
|
2022-07-21 13:01:23 +00:00
|
|
|
import {
|
|
|
|
|
getLintErrorMessage,
|
|
|
|
|
getLintSeverity,
|
|
|
|
|
} from "components/editorComponents/CodeEditor/lintHelpers";
|
2022-08-03 14:35:33 +00:00
|
|
|
import {
|
2022-09-17 17:40:28 +00:00
|
|
|
CustomLintErrorCode,
|
|
|
|
|
CUSTOM_LINT_ERRORS,
|
2022-08-03 14:35:33 +00:00
|
|
|
IGNORED_LINT_ERRORS,
|
2022-11-18 10:23:24 +00:00
|
|
|
INVALID_JSOBJECT_START_STATEMENT,
|
|
|
|
|
JS_OBJECT_START_STATEMENT,
|
2022-08-03 14:35:33 +00:00
|
|
|
SUPPORTED_WEB_APIS,
|
|
|
|
|
} from "components/editorComponents/CodeEditor/constants";
|
2022-09-17 17:40:28 +00:00
|
|
|
import {
|
|
|
|
|
extractInvalidTopLevelMemberExpressionsFromCode,
|
|
|
|
|
isLiteralNode,
|
|
|
|
|
ECMA_VERSION,
|
|
|
|
|
MemberExpressionData,
|
|
|
|
|
} from "@shared/ast";
|
2022-11-03 09:23:15 +00:00
|
|
|
import { getDynamicBindings } from "utils/DynamicBindingUtils";
|
|
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
createGlobalData,
|
|
|
|
|
EvaluationScripts,
|
|
|
|
|
EvaluationScriptType,
|
|
|
|
|
getScriptToEval,
|
|
|
|
|
getScriptType,
|
|
|
|
|
ScriptTemplate,
|
|
|
|
|
} from "workers/Evaluation/evaluate";
|
|
|
|
|
import {
|
|
|
|
|
getEntityNameAndPropertyPath,
|
|
|
|
|
isAction,
|
|
|
|
|
isATriggerPath,
|
|
|
|
|
isJSAction,
|
|
|
|
|
isWidget,
|
|
|
|
|
} from "workers/Evaluation/evaluationUtils";
|
|
|
|
|
import { LintErrors } from "reducers/lintingReducers/lintErrorsReducers";
|
2022-11-18 10:23:24 +00:00
|
|
|
import { Severity } from "entities/AppsmithConsole";
|
2022-12-21 17:14:47 +00:00
|
|
|
import { JSLibraries } from "workers/common/JSLibrary";
|
|
|
|
|
import { MessageType, sendMessage } from "utils/MessageUtil";
|
2022-11-03 09:23:15 +00:00
|
|
|
|
|
|
|
|
export function getlintErrorsFromTree(
|
|
|
|
|
pathsToLint: string[],
|
|
|
|
|
unEvalTree: DataTree,
|
|
|
|
|
): LintErrors {
|
|
|
|
|
const lintTreeErrors: LintErrors = {};
|
|
|
|
|
const GLOBAL_DATA_WITHOUT_FUNCTIONS = createGlobalData({
|
|
|
|
|
dataTree: unEvalTree,
|
|
|
|
|
resolvedFunctions: {},
|
|
|
|
|
isTriggerBased: false,
|
|
|
|
|
});
|
|
|
|
|
// trigger paths
|
|
|
|
|
const triggerPaths = new Set<string>();
|
|
|
|
|
// Certain paths, like JS Object's body are binding paths where appsmith functions are needed in the global data
|
|
|
|
|
const bindingPathsRequiringFunctions = new Set<string>();
|
|
|
|
|
|
|
|
|
|
pathsToLint.forEach((fullPropertyPath) => {
|
|
|
|
|
const { entityName, propertyPath } = getEntityNameAndPropertyPath(
|
|
|
|
|
fullPropertyPath,
|
|
|
|
|
);
|
|
|
|
|
const entity = unEvalTree[entityName];
|
|
|
|
|
const unEvalPropertyValue = (get(
|
|
|
|
|
unEvalTree,
|
|
|
|
|
fullPropertyPath,
|
|
|
|
|
) as unknown) as string;
|
|
|
|
|
// remove all lint errors from path
|
|
|
|
|
set(lintTreeErrors, `["${fullPropertyPath}"]`, []);
|
|
|
|
|
|
|
|
|
|
// We are only interested in paths that require linting
|
|
|
|
|
if (!pathRequiresLinting(unEvalTree, entity, fullPropertyPath)) return;
|
|
|
|
|
if (isATriggerPath(entity, propertyPath))
|
|
|
|
|
return triggerPaths.add(fullPropertyPath);
|
|
|
|
|
if (isJSAction(entity))
|
|
|
|
|
return bindingPathsRequiringFunctions.add(`${entityName}.body`);
|
|
|
|
|
const lintErrors = lintBindingPath(
|
|
|
|
|
unEvalPropertyValue,
|
|
|
|
|
entity,
|
|
|
|
|
fullPropertyPath,
|
|
|
|
|
GLOBAL_DATA_WITHOUT_FUNCTIONS,
|
|
|
|
|
);
|
|
|
|
|
set(lintTreeErrors, `["${fullPropertyPath}"]`, lintErrors);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (triggerPaths.size || bindingPathsRequiringFunctions.size) {
|
|
|
|
|
// we only create GLOBAL_DATA_WITH_FUNCTIONS if there are paths requiring it
|
|
|
|
|
// In trigger based fields, functions such as showAlert, storeValue, etc need to be added to the global data
|
|
|
|
|
const GLOBAL_DATA_WITH_FUNCTIONS = createGlobalData({
|
|
|
|
|
dataTree: unEvalTree,
|
|
|
|
|
resolvedFunctions: {},
|
|
|
|
|
isTriggerBased: true,
|
|
|
|
|
skipEntityFunctions: true,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// lint binding paths that need GLOBAL_DATA_WITH_FUNCTIONS
|
|
|
|
|
if (bindingPathsRequiringFunctions.size) {
|
|
|
|
|
bindingPathsRequiringFunctions.forEach((fullPropertyPath) => {
|
|
|
|
|
const { entityName } = getEntityNameAndPropertyPath(fullPropertyPath);
|
|
|
|
|
const entity = unEvalTree[entityName];
|
|
|
|
|
const unEvalPropertyValue = (get(
|
|
|
|
|
unEvalTree,
|
|
|
|
|
fullPropertyPath,
|
|
|
|
|
) as unknown) as string;
|
|
|
|
|
// remove all lint errors from path
|
|
|
|
|
set(lintTreeErrors, `["${fullPropertyPath}"]`, []);
|
|
|
|
|
const lintErrors = lintBindingPath(
|
|
|
|
|
unEvalPropertyValue,
|
|
|
|
|
entity,
|
|
|
|
|
fullPropertyPath,
|
|
|
|
|
GLOBAL_DATA_WITH_FUNCTIONS,
|
|
|
|
|
);
|
|
|
|
|
set(lintTreeErrors, `["${fullPropertyPath}"]`, lintErrors);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Lint triggerPaths
|
|
|
|
|
if (triggerPaths.size) {
|
|
|
|
|
triggerPaths.forEach((triggerPath) => {
|
|
|
|
|
const { entityName } = getEntityNameAndPropertyPath(triggerPath);
|
|
|
|
|
const entity = unEvalTree[entityName];
|
|
|
|
|
const unEvalPropertyValue = (get(
|
|
|
|
|
unEvalTree,
|
|
|
|
|
triggerPath,
|
|
|
|
|
) as unknown) as string;
|
|
|
|
|
// remove all lint errors from path
|
|
|
|
|
set(lintTreeErrors, `["${triggerPath}"]`, []);
|
|
|
|
|
const lintErrors = lintTriggerPath(
|
|
|
|
|
unEvalPropertyValue,
|
|
|
|
|
entity,
|
|
|
|
|
GLOBAL_DATA_WITH_FUNCTIONS,
|
|
|
|
|
);
|
|
|
|
|
set(lintTreeErrors, `["${triggerPath}"]`, lintErrors);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-10-07 12:33:15 +00:00
|
|
|
|
2022-11-03 09:23:15 +00:00
|
|
|
return lintTreeErrors;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function lintBindingPath(
|
|
|
|
|
dynamicBinding: string,
|
|
|
|
|
entity: DataTreeEntity,
|
|
|
|
|
fullPropertyPath: string,
|
|
|
|
|
globalData: ReturnType<typeof createGlobalData>,
|
|
|
|
|
) {
|
|
|
|
|
let lintErrors: LintError[] = [];
|
2022-11-18 10:23:24 +00:00
|
|
|
|
|
|
|
|
if (isJSAction(entity)) {
|
|
|
|
|
if (!entity.body) return lintErrors;
|
|
|
|
|
if (!entity.body.startsWith(JS_OBJECT_START_STATEMENT)) {
|
|
|
|
|
return lintErrors.concat([
|
|
|
|
|
{
|
|
|
|
|
errorType: PropertyEvaluationErrorType.LINT,
|
|
|
|
|
errorSegment: "",
|
|
|
|
|
originalBinding: entity.body,
|
|
|
|
|
line: 0,
|
|
|
|
|
ch: 0,
|
|
|
|
|
code: entity.body,
|
|
|
|
|
variables: [],
|
|
|
|
|
raw: entity.body,
|
|
|
|
|
errorMessage: INVALID_JSOBJECT_START_STATEMENT,
|
|
|
|
|
severity: Severity.ERROR,
|
|
|
|
|
},
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-03 09:23:15 +00:00
|
|
|
const { propertyPath } = getEntityNameAndPropertyPath(fullPropertyPath);
|
|
|
|
|
// Get the {{binding}} bound values
|
|
|
|
|
const { jsSnippets, stringSegments } = getDynamicBindings(
|
|
|
|
|
dynamicBinding,
|
|
|
|
|
entity,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (stringSegments) {
|
|
|
|
|
jsSnippets.forEach((jsSnippet, index) => {
|
|
|
|
|
if (jsSnippet) {
|
|
|
|
|
const jsSnippetToLint = getJSToLint(entity, jsSnippet, propertyPath);
|
|
|
|
|
// {{user's code}}
|
|
|
|
|
const originalBinding = getJSToLint(
|
|
|
|
|
entity,
|
|
|
|
|
stringSegments[index],
|
|
|
|
|
propertyPath,
|
|
|
|
|
);
|
|
|
|
|
const scriptType = getScriptType(false, false);
|
|
|
|
|
const scriptToLint = getScriptToEval(jsSnippetToLint, scriptType);
|
|
|
|
|
const lintErrorsFromSnippet = getLintingErrors(
|
|
|
|
|
scriptToLint,
|
|
|
|
|
globalData,
|
|
|
|
|
originalBinding,
|
|
|
|
|
scriptType,
|
|
|
|
|
);
|
|
|
|
|
lintErrors = lintErrors.concat(lintErrorsFromSnippet);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return lintErrors;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function lintTriggerPath(
|
|
|
|
|
userScript: string,
|
|
|
|
|
entity: DataTreeEntity,
|
|
|
|
|
globalData: ReturnType<typeof createGlobalData>,
|
|
|
|
|
) {
|
|
|
|
|
const { jsSnippets } = getDynamicBindings(userScript, entity);
|
|
|
|
|
const script = getScriptToEval(jsSnippets[0], EvaluationScriptType.TRIGGERS);
|
|
|
|
|
|
|
|
|
|
return getLintingErrors(
|
|
|
|
|
script,
|
|
|
|
|
globalData,
|
|
|
|
|
jsSnippets[0],
|
|
|
|
|
EvaluationScriptType.TRIGGERS,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function pathRequiresLinting(
|
2022-07-22 20:31:08 +00:00
|
|
|
dataTree: DataTree,
|
|
|
|
|
entity: DataTreeEntity,
|
|
|
|
|
fullPropertyPath: string,
|
2022-11-03 09:23:15 +00:00
|
|
|
): boolean {
|
2022-07-22 20:31:08 +00:00
|
|
|
const { propertyPath } = getEntityNameAndPropertyPath(fullPropertyPath);
|
|
|
|
|
const unEvalPropertyValue = (get(
|
|
|
|
|
dataTree,
|
|
|
|
|
fullPropertyPath,
|
|
|
|
|
) as unknown) as string;
|
|
|
|
|
|
|
|
|
|
if (isATriggerPath(entity, propertyPath)) {
|
|
|
|
|
return isDynamicValue(unEvalPropertyValue);
|
|
|
|
|
}
|
|
|
|
|
const isADynamicBindingPath =
|
|
|
|
|
(isAction(entity) || isWidget(entity) || isJSAction(entity)) &&
|
|
|
|
|
isPathADynamicBinding(entity, propertyPath);
|
|
|
|
|
const requiresLinting =
|
2022-09-17 17:40:28 +00:00
|
|
|
(isADynamicBindingPath && isDynamicValue(unEvalPropertyValue)) ||
|
|
|
|
|
isJSAction(entity);
|
2022-07-22 20:31:08 +00:00
|
|
|
return requiresLinting;
|
2022-11-03 09:23:15 +00:00
|
|
|
}
|
2022-07-22 20:31:08 +00:00
|
|
|
|
2022-09-14 08:35:29 +00:00
|
|
|
// Removes "export default" statement from js Object
|
2022-11-03 09:23:15 +00:00
|
|
|
export function getJSToLint(
|
2022-07-22 20:31:08 +00:00
|
|
|
entity: DataTreeEntity,
|
|
|
|
|
snippet: string,
|
|
|
|
|
propertyPath: string,
|
2022-11-03 09:23:15 +00:00
|
|
|
): string {
|
2022-07-22 20:31:08 +00:00
|
|
|
return entity && isJSAction(entity) && propertyPath === "body"
|
|
|
|
|
? snippet.replace(/export default/g, "")
|
|
|
|
|
: snippet;
|
2022-11-03 09:23:15 +00:00
|
|
|
}
|
2022-07-22 20:31:08 +00:00
|
|
|
|
2022-11-03 09:23:15 +00:00
|
|
|
export function getPositionInEvaluationScript(
|
2021-10-07 12:33:15 +00:00
|
|
|
type: EvaluationScriptType,
|
2022-11-03 09:23:15 +00:00
|
|
|
): Position {
|
2021-10-07 12:33:15 +00:00
|
|
|
const script = EvaluationScripts[type];
|
|
|
|
|
|
|
|
|
|
const index = script.indexOf(ScriptTemplate);
|
2022-04-13 07:00:38 +00:00
|
|
|
const substr = script.slice(0, index !== -1 ? index : 0);
|
2021-10-07 12:33:15 +00:00
|
|
|
const lines = substr.split("\n");
|
|
|
|
|
const lastLine = last(lines) || "";
|
|
|
|
|
|
|
|
|
|
return { line: lines.length, ch: lastLine.length };
|
2022-11-03 09:23:15 +00:00
|
|
|
}
|
2021-10-07 12:33:15 +00:00
|
|
|
|
2021-12-14 08:30:43 +00:00
|
|
|
const EvaluationScriptPositions: Record<string, Position> = {};
|
2021-10-07 12:33:15 +00:00
|
|
|
|
|
|
|
|
function getEvaluationScriptPosition(scriptType: EvaluationScriptType) {
|
2021-12-14 08:30:43 +00:00
|
|
|
if (isEmpty(EvaluationScriptPositions)) {
|
2021-10-07 12:33:15 +00:00
|
|
|
// We are computing position of <<script>> in our templates.
|
|
|
|
|
// This will be used to get the exact location of error in linting
|
|
|
|
|
keys(EvaluationScripts).forEach((type) => {
|
2021-12-14 08:30:43 +00:00
|
|
|
EvaluationScriptPositions[type] = getPositionInEvaluationScript(
|
2021-10-07 12:33:15 +00:00
|
|
|
type as EvaluationScriptType,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-14 08:30:43 +00:00
|
|
|
return EvaluationScriptPositions[scriptType];
|
2021-10-07 12:33:15 +00:00
|
|
|
}
|
2021-10-05 13:52:27 +00:00
|
|
|
|
2022-11-03 09:23:15 +00:00
|
|
|
export function getLintingErrors(
|
2021-10-05 13:52:27 +00:00
|
|
|
script: string,
|
|
|
|
|
data: Record<string, unknown>,
|
2022-09-14 08:35:29 +00:00
|
|
|
// {{user's code}}
|
2021-10-05 13:52:27 +00:00
|
|
|
originalBinding: string,
|
|
|
|
|
scriptType: EvaluationScriptType,
|
2022-11-03 09:23:15 +00:00
|
|
|
): LintError[] {
|
2021-10-07 12:33:15 +00:00
|
|
|
const scriptPos = getEvaluationScriptPosition(scriptType);
|
2021-10-05 13:52:27 +00:00
|
|
|
const globalData: Record<string, boolean> = {};
|
|
|
|
|
for (const dataKey in data) {
|
|
|
|
|
globalData[dataKey] = true;
|
|
|
|
|
}
|
|
|
|
|
// Jshint shouldn't throw errors for additional libraries
|
2022-12-21 17:14:47 +00:00
|
|
|
const libAccessors = ([] as string[]).concat(
|
|
|
|
|
...JSLibraries.map((lib) => lib.accessor),
|
|
|
|
|
);
|
|
|
|
|
libAccessors.forEach((accessor) => (globalData[accessor] = true));
|
|
|
|
|
|
2022-08-03 14:35:33 +00:00
|
|
|
// JSHint shouldn't throw errors for supported web apis
|
|
|
|
|
Object.keys(SUPPORTED_WEB_APIS).forEach(
|
|
|
|
|
(apiName) => (globalData[apiName] = true),
|
|
|
|
|
);
|
2021-10-05 13:52:27 +00:00
|
|
|
|
2022-07-21 13:01:23 +00:00
|
|
|
const options: LintOptions = {
|
2021-10-05 13:52:27 +00:00
|
|
|
indent: 2,
|
2021-12-02 10:03:43 +00:00
|
|
|
esversion: ECMA_VERSION,
|
2021-10-05 13:52:27 +00:00
|
|
|
eqeqeq: false, // Not necessary to use ===
|
|
|
|
|
curly: false, // Blocks can be added without {}, eg if (x) return true
|
|
|
|
|
freeze: true, // Overriding inbuilt classes like Array is not allowed
|
|
|
|
|
undef: true, // Undefined variables should be reported as error
|
|
|
|
|
forin: false, // Doesn't require filtering for..in loops with obj.hasOwnProperty()
|
|
|
|
|
noempty: false, // Empty blocks are allowed
|
|
|
|
|
strict: false, // We won't force strict mode
|
2022-01-28 08:18:19 +00:00
|
|
|
unused: "strict", // Unused variables are not allowed
|
2021-10-05 13:52:27 +00:00
|
|
|
asi: true, // Tolerate Automatic Semicolon Insertion (no semicolons)
|
|
|
|
|
boss: true, // Tolerate assignments where comparisons would be expected
|
|
|
|
|
evil: false, // Use of eval not allowed
|
|
|
|
|
funcscope: true, // Tolerate variable definition inside control statements
|
|
|
|
|
sub: true, // Don't force dot notation
|
2022-02-23 16:19:44 +00:00
|
|
|
expr: true, // suppresses warnings about the use of expressions where normally you would expect to see assignments or function calls
|
2021-10-05 13:52:27 +00:00
|
|
|
// environments
|
|
|
|
|
browser: true,
|
|
|
|
|
worker: true,
|
|
|
|
|
mocha: false,
|
|
|
|
|
// global values
|
|
|
|
|
globals: globalData,
|
2022-03-14 05:27:52 +00:00
|
|
|
loopfunc: true,
|
2021-10-05 13:52:27 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
jshint(script, options);
|
2022-07-22 20:31:08 +00:00
|
|
|
|
2022-11-03 09:23:15 +00:00
|
|
|
const jshintErrors: LintError[] = getValidLintErrors(
|
|
|
|
|
jshint.errors,
|
|
|
|
|
scriptPos,
|
|
|
|
|
).map((lintError) => {
|
|
|
|
|
const ch = lintError.character;
|
|
|
|
|
return {
|
|
|
|
|
errorType: PropertyEvaluationErrorType.LINT,
|
|
|
|
|
raw: script,
|
|
|
|
|
severity: getLintSeverity(lintError.code),
|
|
|
|
|
errorMessage: getLintErrorMessage(lintError.reason),
|
|
|
|
|
errorSegment: lintError.evidence,
|
|
|
|
|
originalBinding,
|
|
|
|
|
// By keeping track of these variables we can highlight the exact text that caused the error.
|
|
|
|
|
variables: [lintError.a, lintError.b, lintError.c, lintError.d],
|
|
|
|
|
code: lintError.code,
|
|
|
|
|
line: lintError.line - scriptPos.line,
|
|
|
|
|
ch: lintError.line === scriptPos.line ? ch - scriptPos.ch : ch,
|
|
|
|
|
};
|
|
|
|
|
});
|
2022-09-17 17:40:28 +00:00
|
|
|
const invalidPropertyErrors = getInvalidPropertyErrorsFromScript(
|
|
|
|
|
script,
|
|
|
|
|
data,
|
|
|
|
|
scriptPos,
|
|
|
|
|
originalBinding,
|
|
|
|
|
);
|
|
|
|
|
return jshintErrors.concat(invalidPropertyErrors);
|
2022-11-03 09:23:15 +00:00
|
|
|
}
|
2022-03-02 06:37:20 +00:00
|
|
|
|
2022-11-03 09:23:15 +00:00
|
|
|
function getValidLintErrors(
|
|
|
|
|
lintErrors: JSHintError[],
|
|
|
|
|
scriptPos: Position,
|
|
|
|
|
): JSHintError[] {
|
|
|
|
|
return lintErrors.reduce((result: JSHintError[], lintError) => {
|
2022-07-21 13:01:23 +00:00
|
|
|
// Ignored errors should not be reported
|
|
|
|
|
if (IGNORED_LINT_ERRORS.includes(lintError.code)) return result;
|
|
|
|
|
/** Some error messages reference line numbers,
|
|
|
|
|
* Eg. Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'
|
|
|
|
|
* these line numbers need to be re-calculated based on the binding location.
|
|
|
|
|
* Errors referencing line numbers outside the user's script should also be ignored
|
|
|
|
|
* */
|
|
|
|
|
let message = lintError.reason;
|
|
|
|
|
const matchedLines = message.match(/line \d/gi);
|
|
|
|
|
const lineNumbersInErrorMessage = new Set<number>();
|
|
|
|
|
let isInvalidErrorMessage = false;
|
|
|
|
|
if (matchedLines) {
|
|
|
|
|
matchedLines.forEach((lineStatement) => {
|
|
|
|
|
const digitString = lineStatement.split(" ")[1];
|
|
|
|
|
const digit = Number(digitString);
|
|
|
|
|
if (isNumber(digit)) {
|
|
|
|
|
if (digit < scriptPos.line) {
|
|
|
|
|
// referenced line number is outside the scope of user's script
|
|
|
|
|
isInvalidErrorMessage = true;
|
|
|
|
|
} else {
|
|
|
|
|
lineNumbersInErrorMessage.add(digit);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (isInvalidErrorMessage) return result;
|
|
|
|
|
if (lineNumbersInErrorMessage.size) {
|
|
|
|
|
Array.from(lineNumbersInErrorMessage).forEach((lineNumber) => {
|
|
|
|
|
message = message.replaceAll(
|
|
|
|
|
`line ${lineNumber}`,
|
|
|
|
|
`line ${lineNumber - scriptPos.line + 1}`,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
result.push({
|
|
|
|
|
...lintError,
|
|
|
|
|
reason: message,
|
|
|
|
|
});
|
|
|
|
|
return result;
|
|
|
|
|
}, []);
|
2022-11-03 09:23:15 +00:00
|
|
|
}
|
2022-09-17 17:40:28 +00:00
|
|
|
|
2022-11-03 09:23:15 +00:00
|
|
|
function getInvalidPropertyErrorsFromScript(
|
2022-09-17 17:40:28 +00:00
|
|
|
script: string,
|
|
|
|
|
data: Record<string, unknown>,
|
|
|
|
|
scriptPos: Position,
|
|
|
|
|
originalBinding: string,
|
2022-11-03 09:23:15 +00:00
|
|
|
): LintError[] {
|
2022-09-17 17:40:28 +00:00
|
|
|
let invalidTopLevelMemberExpressions: MemberExpressionData[] = [];
|
|
|
|
|
try {
|
|
|
|
|
invalidTopLevelMemberExpressions = extractInvalidTopLevelMemberExpressionsFromCode(
|
|
|
|
|
script,
|
|
|
|
|
data,
|
|
|
|
|
self.evaluationVersion,
|
|
|
|
|
);
|
|
|
|
|
} catch (e) {}
|
|
|
|
|
|
|
|
|
|
const invalidPropertyErrors = invalidTopLevelMemberExpressions.map(
|
2022-11-03 09:23:15 +00:00
|
|
|
({ object, property }): LintError => {
|
2022-09-17 17:40:28 +00:00
|
|
|
const propertyName = isLiteralNode(property)
|
2022-11-03 09:23:15 +00:00
|
|
|
? (property.value as string)
|
2022-09-17 17:40:28 +00:00
|
|
|
: property.name;
|
|
|
|
|
const objectStartLine = object.loc.start.line - 1;
|
|
|
|
|
// For computed member expressions (entity["property"]), add an extra 1 to the start column to account for "[".
|
|
|
|
|
const propertyStartColumn = !isLiteralNode(property)
|
|
|
|
|
? property.loc.start.column + 1
|
|
|
|
|
: property.loc.start.column + 2;
|
|
|
|
|
return {
|
|
|
|
|
errorType: PropertyEvaluationErrorType.LINT,
|
|
|
|
|
raw: script,
|
|
|
|
|
severity: getLintSeverity(CustomLintErrorCode.INVALID_ENTITY_PROPERTY),
|
|
|
|
|
errorMessage: CUSTOM_LINT_ERRORS[
|
|
|
|
|
CustomLintErrorCode.INVALID_ENTITY_PROPERTY
|
|
|
|
|
](object.name, propertyName),
|
|
|
|
|
errorSegment: `${object.name}.${propertyName}`,
|
|
|
|
|
originalBinding,
|
|
|
|
|
variables: [propertyName, null, null, null],
|
|
|
|
|
code: CustomLintErrorCode.INVALID_ENTITY_PROPERTY,
|
|
|
|
|
line: objectStartLine - scriptPos.line,
|
|
|
|
|
ch:
|
|
|
|
|
objectStartLine === scriptPos.line
|
|
|
|
|
? propertyStartColumn - scriptPos.ch
|
|
|
|
|
: propertyStartColumn,
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
return invalidPropertyErrors;
|
2022-11-03 09:23:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function initiateLinting(
|
|
|
|
|
lintOrder: string[],
|
|
|
|
|
unevalTree: DataTree,
|
|
|
|
|
requiresLinting: boolean,
|
|
|
|
|
) {
|
|
|
|
|
if (!requiresLinting) return;
|
2022-12-21 17:14:47 +00:00
|
|
|
sendMessage.call(self, {
|
|
|
|
|
messageId: "",
|
|
|
|
|
messageType: MessageType.REQUEST,
|
|
|
|
|
body: {
|
|
|
|
|
data: {
|
|
|
|
|
lintOrder,
|
|
|
|
|
unevalTree,
|
|
|
|
|
},
|
|
|
|
|
method: MAIN_THREAD_ACTION.LINT_TREE,
|
2022-11-03 09:23:15 +00:00
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|