Skip to content

Commit ae5ad50

Browse files
committed
feat: add HTL data-sly-test lint reference to best-practices
Made-with: Cursor
1 parent b4dad8d commit ae5ad50

4 files changed

Lines changed: 343 additions & 27 deletions

File tree

skills/aem/cloud-service/skills/best-practices/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# AEM as a Cloud Service — Best practices
22

3-
Source: `skills/aem/cloud-service/skills/best-practices/`. **`SKILL.md`** and **`references/`** (patterns: scheduler, replication, events, assets; Java baseline: `scr-to-osgi-ds.md`, `resource-resolver-logging.md`, prerequisites hub).
3+
Source: `skills/aem/cloud-service/skills/best-practices/`. **`SKILL.md`** and **`references/`** (patterns: scheduler, replication, events, assets; Java baseline: `scr-to-osgi-ds.md`, `resource-resolver-logging.md`, prerequisites hub; **HTL:** `data-sly-test-redundant-constant.md` and proactive `rg` discovery in `SKILL.md`).
44

55
These files ship with the **AEM as a Cloud Service** plugin (`aem-cloud-service` in the marketplace). Install that umbrella package once; the agent selects this skill when the task matches.
66

skills/aem/cloud-service/skills/best-practices/SKILL.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
---
22
name: best-practices
3-
description: AEM as a Cloud Service Java/OSGi best practices, guardrails, and legacy-to-cloud pattern transformations. Use for Cloud Service–correct bundles, deprecated APIs, schedulers, ResourceChangeListener, replication, Replicator, JCR observation (javax.jcr.observation.EventListener), OSGi Event Admin (org.osgi.service.event.EventHandler), DAM AssetManager, BPA-style fixes, or any time you need the detailed pattern reference modules under this skill.
3+
description: AEM as a Cloud Service Java/OSGi best practices, guardrails, and legacy-to-cloud pattern transformations. Use for Cloud Service–correct bundles, deprecated APIs, schedulers, ResourceChangeListener, replication, Replicator, JCR observation (javax.jcr.observation.EventListener), OSGi Event Admin (org.osgi.service.event.EventHandler), DAM AssetManager, BPA-style fixes, HTL (Sightly) Cloud SDK lint warnings (data-sly-test redundant constant value comparison), or any time you need the detailed pattern reference modules under this skill.
44
---
55

66
# AEM as a Cloud Service — Best Practices
77

8-
Platform guidance for **AEM as a Cloud Service** backend code: what to use, what to avoid, and **how to refactor** known legacy patterns into Cloud-compatible implementations.
8+
Platform guidance for **AEM as a Cloud Service**: **Java/OSGi** (what to use, what to avoid, how to refactor legacy patterns) and **HTL** (component `.html` templates, Cloud SDK HTL lint).
99

1010
This skill holds the **pattern transformation modules** (`references/*.md`). They ship with the **`aem-cloud-service`** plugin; use this skill **without** the **migration** skill for greenfield or maintenance work that only needs these references. Use **migration** when you need BPA/CAM orchestration on top.
1111

12-
**Quick pick:** Open the **Pattern Reference Modules** table below → jump to the matching `references/<file>.md` → read it fully before editing Java. For Felix SCR, resolvers, or logging, use **Java / OSGi baseline** links in this file first when those appear in the same change set.
12+
**Quick pick:** Open the **Pattern Reference Modules** table below → jump to the matching `references/<file>.md` → read it fully before editing. For Java: Felix SCR, resolvers, or logging, use **Java / OSGi baseline** links first when those appear in the same change set.
1313

1414
## When to Use This Skill
1515

1616
Use this skill when you need to:
1717

18-
- Apply **AEM as a Cloud Service** constraints to Java/OSGi code (new or existing)
19-
- Refactor **legacy patterns** into supported APIs (same modules migration uses)
18+
- Apply **AEM as a Cloud Service** constraints to **Java/OSGi** code (new or existing)
19+
- Refactor **legacy Java patterns** into supported APIs (same modules migration uses)
2020
- Follow **consistent rules** across schedulers, replication, **JCR observation listeners** (`eventListener`), **OSGi event handlers** (`eventHandler`), and DAM assets
21+
- Fix **HTL (Sightly)** issues from the **AEM Cloud SDK build**, especially `data-sly-test: redundant constant value comparison`
2122
- Read **step-by-step transformation** and validation checklists for a specific pattern
2223

2324
For **BPA/CAM orchestration** (collections, CSV, MCP project selection), use the **`migration`** skill (`skills/aem/cloud-service/skills/migration/`).
@@ -36,6 +37,7 @@ Each supported pattern has a dedicated module under `references/` relative to th
3637
| Asset Manager | `assetApi` | `references/asset-manager.md` | Ready |
3738
| Felix SCR → OSGi DS || `references/scr-to-osgi-ds.md` | Ready |
3839
| ResourceResolver + SLF4J || `references/resource-resolver-logging.md` | Ready |
40+
| HTL: `data-sly-test` redundant constant | — (HTL lint) | `references/data-sly-test-redundant-constant.md` | Ready |
3941
| *(Prerequisites hub)* || `references/aem-cloud-service-pattern-prerequisites.md` ||
4042

4143
**Event listener vs event handler (not the same):** **`eventListener`** is **JCR observation** — the JCR API for repository change callbacks (`javax.jcr.observation.EventListener`, `onEvent`). **`eventHandler`** is **OSGi Event Admin** — whiteboard-style OSGi events (`org.osgi.service.event.EventHandler`, `handleEvent`). Both migrate via **`references/event-migration.md`** (Path A vs Path B). **`resourceChangeListener`** is separate: Sling **`ResourceChangeListener`**, module **`references/resource-change-listener.md`**.
@@ -56,11 +58,11 @@ SCR→DS and `ResourceResolver`/logging are **reference modules** under `referen
5658
- **READ THE PATTERN MODULE FIRST** — never transform code without reading the module
5759
- **READ** [`scr-to-osgi-ds.md`](references/scr-to-osgi-ds.md) and [`resource-resolver-logging.md`](references/resource-resolver-logging.md) when SCR, `ResourceResolver`, or logging are in scope (pattern modules link via the [prerequisites hub](references/aem-cloud-service-pattern-prerequisites.md); do not duplicate long guides inline)
5860
- **DO** preserve environment-specific guards (e.g. `isAuthor()` run mode checks)
59-
- **DO NOT** change business logic inside methods
61+
- **DO NOT** change business logic inside methods (Java) or **logical show/hide intent** (HTL) unless the module explicitly allows it
6062
- **DO NOT** rename classes unless the pattern module explicitly says to
6163
- **DO NOT** invent values — extract from existing code
6264
- **DO NOT** edit files outside the scope agreed with the user (e.g. only BPA targets or paths they named)
63-
- **DO** keep **searches, discovery, and edits** for the customers AEM sources inside the **IDE workspace root(s)** currently open; **DO NOT** grep or walk directories outside that boundary to find Java unless the user explicitly points there
65+
- **DO** keep **searches, discovery, and edits** for the customer's AEM sources inside the **IDE workspace root(s)** currently open; **DO NOT** grep or walk directories outside that boundary to find Java unless the user explicitly points there
6466

6567
## Manual Pattern Hints (Classification)
6668

@@ -76,9 +78,10 @@ When no BPA list exists, scan imports and types to pick a module:
7678
| `com.day.cq.dam.api.AssetManager` create/remove asset APIs | `assetApi` |
7779
| `org.apache.felix.scr.annotations` | read `references/scr-to-osgi-ds.md` (often combined with a BPA pattern) |
7880
| `getAdministrativeResourceResolver`, `System.out` / `printStackTrace` | read `references/resource-resolver-logging.md` |
81+
| **HTL:** build warning `data-sly-test: redundant constant value comparison`, or `.html` under `ui.apps` / `jcr_root` with bad `data-sly-test` | read `references/data-sly-test-redundant-constant.md` |
7982

8083
If multiple patterns match, ask which to fix first.
8184

8285
## Relationship to Migration
8386

84-
The **`migration`** skill defines **one-pattern-per-session** workflow, BPA/CAM/MCP flows, and user messaging. It **delegates** all detailed transformation steps to this skills `references/` modules. It uses a **`{best-practices}`** repo-root path alias to this folder (see its `SKILL.md`). Keep platform truth here; keep orchestration there.
87+
The **`migration`** skill defines **one-pattern-per-session** workflow, BPA/CAM/MCP flows, and user messaging. It **delegates** all detailed transformation steps to this skill's `references/` modules. It uses a **`{best-practices}`** repo-root path alias to this folder (see its `SKILL.md`). Keep platform truth here; keep orchestration there.
Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
# data-sly-test: Redundant Constant Value Comparison
2+
3+
Fixes for the AEM Cloud SDK HTL lint warning **"data-sly-test: redundant constant value comparison"**.
4+
5+
**Rule:** In HTL, `data-sly-test` should always receive a variable or expression that evaluates to boolean — never a raw string literal, raw `true`/`false`, or a number directly.
6+
7+
---
8+
9+
## Workflow
10+
11+
When the user pastes **HTL lint warnings** or asks to fix **`data-sly-test`** issues:
12+
13+
1. **Group warnings by file** (de-duplicate repeated lines from the build).
14+
2. **Classify** each warning using the flagged value (`true`/`false`, path string, number, split `${} || ${}`) — see patterns below.
15+
3. **Read the matching pattern section** before editing `.html` files.
16+
4. **Preserve logical intent** — only fix expression shape, not authoring behavior.
17+
18+
## Proactive Discovery (find candidates without Maven)
19+
20+
When the user wants to **find and fix** redundant `data-sly-test` issues but has **no build log yet**, run a **heuristic scan** on HTL templates, then fix hits using the patterns below.
21+
22+
**Limits:** Grep is **not** the HTL compiler. It can **miss** issues (multiline attributes, unusual quoting) and **false-positive** on expressions that are fine. After edits, **`mvn … generate-sources`** (or the Cloud Manager build) is still the **authoritative** check.
23+
24+
**Scope:** Prefer `ui.apps/**/jcr_root/**/*.html` and any other content packages that ship component HTL.
25+
26+
From the **repository root**, use **ripgrep** (`rg`):
27+
28+
| Pattern class | What to search | Example `rg` (run from repo root) |
29+
|---------------|----------------|-------------------------------------|
30+
| Boolean literal | `== true`, `== false`, `!= true`, `!= false` inside `data-sly-test` | `rg -n 'data-sly-test="[^"]*==\s*(true|false)' --glob '*.html' ui.apps` and `rg -n 'data-sly-test="[^"]*!=\s*(true|false)' --glob '*.html' ui.apps` |
31+
| Raw `/apps/…` string as test | HTL string literal starting with `/apps/` inside the attribute (common anti-pattern) | `rg -n "data-sly-test=.*'/apps/" --glob '*.html' ui.apps` (then confirm it is a useless string-as-test, not a legitimate comparison) |
32+
| Split `\|\|` / `&&` across `${}` | Literal `} || ${` or `} && ${` in one attribute | `rg -n 'data-sly-test="[^"]*\}\s*\|\|\s*\$\{' --glob '*.html' ui.apps` and the same pattern with `&&` instead of `\|\|` |
33+
| Numeric literal comparison | `== <digits>` inside a `data-sly-test` line (review each hit) | `rg -n 'data-sly-test="[^"]*==\s*[0-9]+\s*}' --glob '*.html' ui.apps` |
34+
35+
**Agent workflow:**
36+
37+
1. Run the `rg` lines above (adjust `ui.apps` to the user's module paths if different).
38+
2. Open each file at the reported lines; confirm the match is really a `data-sly-test` problem per the patterns below (skip unrelated `== 1` in other attributes if any).
39+
3. Apply fixes from the matching pattern section.
40+
4. Tell the user to run **HTL validate** / full module build so compiler warnings confirm nothing was missed.
41+
42+
---
43+
44+
## Pattern 1: Boolean Constant Comparison (`== true` / `== false`)
45+
46+
### What the linter flags
47+
48+
The warning value is `true` or `false`. The HTL expression compares a variable against a boolean literal.
49+
50+
### Before (warns)
51+
52+
```html
53+
<div data-sly-test="${someVar == true}">visible when someVar is truthy</div>
54+
<div data-sly-test="${someVar == false}">visible when someVar is falsy</div>
55+
```
56+
57+
### After (clean)
58+
59+
```html
60+
<div data-sly-test="${someVar}">visible when someVar is truthy</div>
61+
<div data-sly-test="${!someVar}">visible when someVar is falsy</div>
62+
```
63+
64+
### Rules
65+
66+
- `== true` → remove the comparison; HTL treats the variable as boolean by default
67+
- `== false` → negate with `!`
68+
- If the original uses `data-sly-test.varName`, preserve the variable name:
69+
70+
```html
71+
<!-- Before -->
72+
<div data-sly-test.isActive="${model.active == true}">
73+
74+
<!-- After -->
75+
<div data-sly-test.isActive="${model.active}">
76+
```
77+
78+
### Edge case: `!= true` / `!= false`
79+
80+
```html
81+
<!-- != true is the same as == false -->
82+
<div data-sly-test="${someVar != true}"> → <div data-sly-test="${!someVar}">
83+
84+
<!-- != false is the same as == true -->
85+
<div data-sly-test="${someVar != false}"> → <div data-sly-test="${someVar}">
86+
```
87+
88+
---
89+
90+
## Pattern 2: Hardcoded String/Path as Test Value
91+
92+
### What the linter flags
93+
94+
The warning value is a string like `/apps/bcbsmgeneral/components/content/container`. A raw string literal inside `data-sly-test` is always truthy (non-empty string), so the condition is meaningless.
95+
96+
### Diagnosis
97+
98+
The author almost always **meant** to compare a variable against that string. Common cases:
99+
100+
| Intent | Variable to compare |
101+
|--------|---------------------|
102+
| Check current resource type | `resource.resourceType` |
103+
| Check a child resource type | `childResource.resourceType` |
104+
| Check a property value | `properties.someProperty` |
105+
| Check a `data-sly-resource` path | a variable holding the resource type |
106+
107+
### Before (warns)
108+
109+
```html
110+
<!-- Always truthy — the string itself is the test -->
111+
<div data-sly-test="${'/apps/bcbsmgeneral/components/content/container'}">
112+
...
113+
</div>
114+
```
115+
116+
### After (clean)
117+
118+
Determine **what variable** should be compared. The most common fix:
119+
120+
```html
121+
<!-- Compare resource type (strip /apps/ prefix for sling:resourceType) -->
122+
<div data-sly-test="${resource.resourceType == 'bcbsmgeneral/components/content/container'}">
123+
...
124+
</div>
125+
```
126+
127+
Or if the path was being passed to `data-sly-resource`:
128+
129+
```html
130+
<!-- The test was guarding a data-sly-resource include -->
131+
<sly data-sly-set.containerType="${'bcbsmgeneral/components/content/container'}"/>
132+
<div data-sly-test="${containerType}"
133+
data-sly-resource="${resource @ resourceType=containerType}">
134+
</div>
135+
```
136+
137+
### Rules
138+
139+
- **Never** leave a raw string as the sole `data-sly-test` value
140+
- Strip the `/apps/` prefix when comparing against `resource.resourceType` (Sling stores relative resource types)
141+
- If you cannot determine the intended variable, wrap in `data-sly-set` and add a `TODO` comment:
142+
143+
```html
144+
<!-- TODO: verify the correct variable for this comparison -->
145+
<sly data-sly-set.expectedType="${'bcbsmgeneral/components/content/container'}"/>
146+
<div data-sly-test="${resource.resourceType == expectedType}">
147+
```
148+
149+
### Nested resource iteration
150+
151+
When iterating child resources (e.g. `data-sly-list`), the comparison often belongs on the child:
152+
153+
```html
154+
<!-- Before (warns — raw path string) -->
155+
<sly data-sly-list.child="${resource.children}">
156+
<div data-sly-test="${'/apps/bcbsmgeneral/components/content/container'}">
157+
<sly data-sly-resource="${child}"/>
158+
</div>
159+
</sly>
160+
161+
<!-- After -->
162+
<sly data-sly-list.child="${resource.children}">
163+
<div data-sly-test="${child.resourceType == 'bcbsmgeneral/components/content/container'}">
164+
<sly data-sly-resource="${child}"/>
165+
</div>
166+
</sly>
167+
```
168+
169+
---
170+
171+
## Pattern 3: Numeric Constant Comparison
172+
173+
### What the linter flags
174+
175+
The warning value is a number like `1` or `2`. HTL's linter flags numeric literals in comparisons even when the logic is correct.
176+
177+
### Before (warns)
178+
179+
```html
180+
<div data-sly-test="${properties.columnCount == 1}">one column</div>
181+
<div data-sly-test="${properties.columnCount == 2}">two columns</div>
182+
```
183+
184+
### After (clean)
185+
186+
Extract the comparison into a `data-sly-set` variable:
187+
188+
```html
189+
<sly data-sly-set.isOneColumn="${properties.columnCount == 1}"/>
190+
<sly data-sly-set.isTwoColumn="${properties.columnCount == 2}"/>
191+
192+
<div data-sly-test="${isOneColumn}">one column</div>
193+
<div data-sly-test="${isTwoColumn}">two columns</div>
194+
```
195+
196+
### Rules
197+
198+
- Move the numeric comparison into `data-sly-set` so `data-sly-test` only sees a boolean variable
199+
- Choose descriptive variable names (`isOneColumn`, `isTypeTwo`, etc.)
200+
- Place `data-sly-set` on a `<sly>` element **before** the element that uses `data-sly-test`
201+
- If many numeric comparisons exist (e.g. a switch-like block), group all `data-sly-set` declarations together at the top
202+
203+
### Alternative: Use a Sling Model
204+
205+
For complex switch logic, expose a method from the model:
206+
207+
```java
208+
public boolean isOneColumn() {
209+
return getColumnCount() == 1;
210+
}
211+
```
212+
213+
```html
214+
<div data-sly-test="${model.oneColumn}">one column</div>
215+
```
216+
217+
---
218+
219+
## Pattern 4: Split-Expression Logical OR/AND
220+
221+
### What the linter flags
222+
223+
The warning value is something like `${properties.videoUrl} || ${properties.videoUrl1}`. HTL does **not** support `||` or `&&` operators **across** separate `${}` expression blocks.
224+
225+
When you write:
226+
227+
```html
228+
<div data-sly-test="${properties.videoUrl} || ${properties.videoUrl1}">
229+
```
230+
231+
HTL evaluates `${properties.videoUrl}` as one expression and treats the literal string ` || ` and `${properties.videoUrl1}` as separate tokens. The result is unpredictable and always flagged.
232+
233+
### Before (warns)
234+
235+
```html
236+
<div data-sly-test="${properties.videoUrl} || ${properties.videoUrl1}">
237+
video content
238+
</div>
239+
```
240+
241+
### After (clean)
242+
243+
Combine into a **single** `${}` block:
244+
245+
```html
246+
<sly data-sly-set.hasVideo="${properties.videoUrl || properties.videoUrl1}"/>
247+
<div data-sly-test="${hasVideo}">
248+
video content
249+
</div>
250+
```
251+
252+
Or inline if the expression is short:
253+
254+
```html
255+
<div data-sly-test="${properties.videoUrl || properties.videoUrl1}">
256+
video content
257+
</div>
258+
```
259+
260+
### Rules
261+
262+
- **All** logical operators (`||`, `&&`, ternary `? :`) must be inside a **single** `${}` block
263+
- If the combined expression is long, use `data-sly-set` for readability
264+
- The same applies to `&&`:
265+
266+
```html
267+
<!-- BAD -->
268+
<div data-sly-test="${properties.a} && ${properties.b}">
269+
270+
<!-- GOOD -->
271+
<div data-sly-test="${properties.a && properties.b}">
272+
```
273+
274+
---
275+
276+
## Validation Checklist
277+
278+
After fixing all warnings in a file:
279+
280+
- [ ] **Build passes** — re-run `mvn clean install` and confirm no `redundant constant value comparison` warnings remain for this file
281+
- [ ] **Logical intent preserved** — the element still shows/hides under the same conditions as before
282+
- [ ] **No broken references** — if `data-sly-test.varName` was changed, all downstream `${varName}` references still work
283+
- [ ] **No orphaned `data-sly-set`** — every `data-sly-set` variable is used by at least one `data-sly-test`
284+
- [ ] **Authoring still works** — component dialog values still drive the correct rendering in author and publish mode
285+
286+
## Files That Cannot Be Auto-Fixed
287+
288+
Some warnings require **human judgment**:
289+
290+
- **Pattern 2** when the intended comparison variable is ambiguous (no obvious `resource.resourceType` or `properties.*` candidate)
291+
- **Expressions inside `data-sly-include` or `data-sly-template`** — the scope of variables may differ
292+
- **Third-party / ACS Commons overlays** — changing shared component HTL may break other sites on the same instance
293+
294+
Flag these to the user with a `TODO` comment and move on.

0 commit comments

Comments
 (0)