Skip to content

Commit

Permalink
feat(binding): Detect global formatter notation in bindings
Browse files Browse the repository at this point in the history
Reuse UI5 runtime Binding- and ExpressionParser to extract formatter
definition and other information from bindings defined in JS or XML.
  • Loading branch information
RandomByte committed Jan 16, 2025
1 parent 48aeab0 commit 0845caf
Show file tree
Hide file tree
Showing 23 changed files with 2,814 additions and 36 deletions.
4 changes: 3 additions & 1 deletion .nycrc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
"exclude": [
"src/cli/middlewares/updateNotifier.ts",
"src/cli/utils/profile.ts",
"src/linter/xmlTemplate/lib/JSTokenizer.js"
"src/linter/xmlTemplate/lib/JSTokenizer.js",
"src/linter/binding/lib/**/*.js",
"**/*.d.ts"
],
"check-coverage": true,
"statements": 87,
Expand Down
25 changes: 25 additions & 0 deletions .reuse/dep5
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,31 @@ Copyright: 2009-2024 SAP SE or an SAP affiliate company
License: Apache-2.0
Comment: This file is a copy of sap/base/util/JSTokenizer.js from the OpenUI5 project.

Files: src/linter/binding/lib/BindingParser.js
Copyright: 2009-2025 SAP SE or an SAP affiliate company
License: Apache-2.0
Comment: This file is a copy of sap/ui/base/BindingParser.js from the OpenUI5 project.

Files: src/linter/binding/lib/ExpressionParser.js
Copyright: 2009-2025 SAP SE or an SAP affiliate company
License: Apache-2.0
Comment: This file is a copy of sap/ui/base/ExpressionParser.js from the OpenUI5 project.

Files: src/linter/binding/lib/BindingMode.js
Copyright: 2009-2025 SAP SE or an SAP affiliate company
License: Apache-2.0
Comment: This file is a copy of sap/ui/model/BindingMode.js from the OpenUI5 project.

Files: src/linter/binding/lib/util/deepEqual.js
Copyright: 2009-2025 SAP SE or an SAP affiliate company
License: Apache-2.0
Comment: This file is a copy of sap/base/util/deepEqual.js from the OpenUI5 project.

Files: src/linter/binding/lib/strings/escapeRegExp.js
Copyright: 2009-2025 SAP SE or an SAP affiliate company
License: Apache-2.0
Comment: This file is a copy of sap/base/strings/escapeRegExp.js from the OpenUI5 project.

Files: src/formatter/lib/resolveLinks.ts
Copyright: 2009-2024 SAP SE or an SAP affiliate company
License: Apache-2.0
Expand Down
67 changes: 67 additions & 0 deletions src/linter/binding/BindingLinter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import LinterContext, {PositionInfo, ResourcePath} from "../LinterContext.js";
import {MESSAGE} from "../messages.js";
import {RequireDeclaration} from "../xmlTemplate/Parser.js";
import BindingParser, {BindingInfo} from "./lib/BindingParser.js";

export default class BindingLinter {
#resourcePath: string;
#context: LinterContext;

constructor(resourcePath: ResourcePath, context: LinterContext) {
this.#resourcePath = resourcePath;
this.#context = context;
}

#parseBinding(binding: string): BindingInfo {
const bindingInfo = BindingParser.complexParser(binding, null, true, true, true, true);
return bindingInfo;
}

#analyzeBinding(bindingInfo: BindingInfo, requireDeclarations: RequireDeclaration[], position: PositionInfo) {
const {formatter, events} = bindingInfo;
if (formatter) {
this.#checkForGlobalReference(formatter, requireDeclarations, position);
}
if (events && typeof events === "object") {
for (const eventHandler of Object.values(events)) {
this.#checkForGlobalReference(eventHandler, requireDeclarations, position);
}
}
}

#checkForGlobalReference(ref: string, requireDeclarations: RequireDeclaration[], position: PositionInfo) {
if (ref.startsWith(".")) {
// Ignore empty reference or reference to the controller (as indicated by the leading dot)
return false;
}
const parts = ref.split(".");
let variableName;
if (parts.length) {
variableName = parts[0];
} else {
variableName = ref;
}
const requireDeclaration = requireDeclarations.find((decl) => decl.variableName === variableName);
if (requireDeclaration) {
return false;
}

// Global reference detected
this.#context.addLintingMessage(this.#resourcePath, MESSAGE.NO_GLOBALS, {
variableName,
namespace: ref,
}, position);
}

lintPropertyBinding(bindingDefinition: string, requireDeclarations: RequireDeclaration[], position: PositionInfo) {
try {
const bindingInfo = this.#parseBinding(bindingDefinition);
if (bindingInfo) {
this.#analyzeBinding(bindingInfo, requireDeclarations, position);
}
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
this.#context.addLintingMessage(this.#resourcePath, MESSAGE.PARSING_ERROR, {message}, position);
}
}
}
41 changes: 41 additions & 0 deletions src/linter/binding/lib/BindingMode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* This is a copy of the sap/ui/model/BindingMode.js module of the OpenUI5 project
* https://github.com/SAP/openui5/blob/a4507f0d4f8a56cc881e8983479c8f9b21bfb96b/src/sap.ui.core/src/sap/ui/model/BindingMode.js
*/

/**
* Binding type definitions.
*
* @enum {string}
* @public
* @alias sap.ui.model.BindingMode
*/
var BindingMode = {

/**
* BindingMode default means that the binding mode of the model is used
* @public
*/
Default: "Default",

/**
* BindingMode one time means value is only read from the model once
* @public
*/
OneTime: "OneTime",

/**
* BindingMode one way means from model to view
* @public
*/
OneWay: "OneWay",

/**
* BindingMode two way means from model to view and vice versa
* @public
*/
TwoWay: "TwoWay",

};

export default BindingMode;
23 changes: 23 additions & 0 deletions src/linter/binding/lib/BindingParser.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export interface BindingInfo {
path?: string;
model?: string;
formatter?: string;
events?: Record<string, string>;
}

interface BindingParser {
complexParser: (
sString: string,
oContext: object | null,
bUnescape?: boolean,
bTolerateFunctionsNotFound?: boolean,
bStaticContext?: boolean,
bPreferContext?: boolean,
mLocals?: Record<string, string>,
bResolveTypesAsync?: boolean
) => BindingInfo;
}

declare const BindingParser: BindingParser;

export default BindingParser;
Loading

0 comments on commit 0845caf

Please sign in to comment.