Skip to content

Commit 00a97aa

Browse files
committed
feat: enhance structure matching and transformation capabilities
- Updated the store to include new methods for handling known structure matches, including `getKnownStructureMatchNodes` and `getKnownStructureMatchShape`. - Introduced a new template type for deleting structure matches with customizable run settings, allowing users to specify run modes and iteration counts. - Enhanced the TransformEditor and TemplateWorkbench components to support the new delete structure matches functionality, including UI elements for selecting run modes and iterations. - Added utility functions in the restringer integration to describe match shapes and collect matched nodes, improving the developer experience for custom transformations.
1 parent 2027e86 commit 00a97aa

8 files changed

Lines changed: 925 additions & 626 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# flASTer <img src="dist/favicon.svg">
2-
A [web interface](https://ctrl-escp.github.io/flaster/) for exploring JS code structures using [flAST](https://github.com/perimeterx/flast)
2+
A [web interface](https://ctrl-escp.github.io/flaster/) for exploring JS code structures using [flAST](https://github.com/ctrl-escp/flast)
33

44
Helpful in investigating code structures when obfuscating and deobfuscating JS.
55

package-lock.json

Lines changed: 126 additions & 566 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,4 @@
3333
"bugs": {
3434
"url": "https://github.com/ctrl-escp/flaster/issues"
3535
}
36-
}
36+
}

src/TransformEditor.vue

Lines changed: 80 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,24 @@ import IconReset from './components/icons/IconReset.vue';
77
import IconTrash from './components/icons/IconTrash.vue';
88
import IconTransform from './components/icons/IconTransform.vue';
99
10-
const initialValue = `// write the logic to apply to the node \`(n) => {<your code>}\`.
11-
// to delete a node use arb.markNode(n);
12-
// to replace a node with a new 'replacementNode' use arb.markNode(n, replacementNode);
13-
const replacements = {Hello: 'General', there: 'Kenobi'};
14-
if (replacements[n.value]) arb.markNode(n, {
15-
type: 'Literal',
16-
value: replacements[n.value]
17-
});
10+
const initialValue = `// Known structure mode:
11+
// The body runs once per pass with \`matches\`, the raw array returned by the matcher.
12+
// Example: for (const match of matches) { ... }
13+
//
14+
// Filter mode:
15+
// The body runs once per matched node with \`n\`.
16+
// Example: arb.markNode(n);
1817
`;
1918
2019
const activeStructure = computed(() =>
2120
store.getKnownStructureById(store.inspectedKnownStructureId ?? store.activeKnownStructureId));
21+
const activeMatchShape = computed(() =>
22+
activeStructure.value ? store.getKnownStructureMatchShape(activeStructure.value.id) : null);
2223
const runSettings = computed(() => store.templateDrafts['advanced-js-step'] ?? {});
2324
const activeFilterCount = computed(() => store.filters.filter((filter) => filter?.enabled).length);
2425
const transformContext = computed(() =>
2526
activeStructure.value
26-
? `Runs against nodes matched by ${activeStructure.value.title}`
27+
? `Runs against the raw match array returned by ${activeStructure.value.title}`
2728
: activeFilterCount.value
2829
? `Runs against nodes that match ${activeFilterCount.value} active filters`
2930
: 'Runs against the current result set when no filters are active');
@@ -110,7 +111,21 @@ function revertTransformation() {
110111
</div>
111112
</div>
112113
<div class="editor-shell editor-shell-lg">
114+
<div class="editor-frame-code editor-frame-code-top">function customTransform(arb, matches) {</div>
113115
<code-editor :editor-id="store.editorIds.transformEditor" :initial-value="initialValue" />
116+
<div class="editor-frame-code editor-frame-code-bottom"> return arb;
117+
}</div>
118+
</div>
119+
<div v-if="activeStructure" class="match-shape-card">
120+
<p class="match-shape-title">Known structure match payload</p>
121+
<p class="match-shape-copy">
122+
Your custom transform body receives <code>matches</code>, which is the raw array returned by the active
123+
matcher before flASTer normalizes it.
124+
</p>
125+
<pre class="match-shape-code">{{ activeMatchShape?.sample || '{ unknown }' }}</pre>
126+
<p v-if="activeMatchShape?.keys?.length" class="match-shape-copy">
127+
Top-level keys on each match entry: {{ activeMatchShape.keys.join(', ') }}
128+
</p>
114129
</div>
115130
<div class="run-controls">
116131
<label class="run-field">
@@ -172,7 +187,8 @@ function revertTransformation() {
172187
173188
.section-copy,
174189
.panel-meta,
175-
.card-note {
190+
.card-note,
191+
.match-shape-copy {
176192
color: var(--text-muted);
177193
}
178194
@@ -213,6 +229,32 @@ function revertTransformation() {
213229
min-height: 0;
214230
}
215231
232+
.editor-frame-code {
233+
font-family: var(--font-mono, monospace);
234+
font-size: 0.92rem;
235+
line-height: 1.5;
236+
color: var(--text-muted);
237+
background: rgba(255, 255, 255, 0.02);
238+
border-left: 1px solid var(--panel-border);
239+
border-right: 1px solid var(--panel-border);
240+
padding: 0.55rem 0.85rem;
241+
white-space: pre-wrap;
242+
}
243+
244+
.editor-frame-code-top {
245+
border-top: 1px solid var(--panel-border);
246+
border-top-left-radius: 0.9rem;
247+
border-top-right-radius: 0.9rem;
248+
border-bottom: 0;
249+
}
250+
251+
.editor-frame-code-bottom {
252+
border-bottom: 1px solid var(--panel-border);
253+
border-bottom-left-radius: 0.9rem;
254+
border-bottom-right-radius: 0.9rem;
255+
border-top: 0;
256+
}
257+
216258
.run-controls {
217259
display: flex;
218260
gap: 0.7rem;
@@ -253,6 +295,34 @@ function revertTransformation() {
253295
min-height: 16rem;
254296
}
255297
298+
.match-shape-card {
299+
border: 1px solid rgba(255, 255, 255, 0.08);
300+
border-radius: 12px;
301+
background: rgba(255, 255, 255, 0.03);
302+
padding: 0.75rem;
303+
display: flex;
304+
flex-direction: column;
305+
gap: 0.4rem;
306+
}
307+
308+
.match-shape-title {
309+
font-size: 0.86rem;
310+
font-weight: 600;
311+
letter-spacing: 0.02em;
312+
text-transform: uppercase;
313+
color: var(--text-primary);
314+
}
315+
316+
.match-shape-code {
317+
margin: 0;
318+
white-space: pre-wrap;
319+
word-break: break-word;
320+
border-radius: 10px;
321+
padding: 0.7rem;
322+
background: rgba(3, 8, 16, 0.72);
323+
color: #c8defa;
324+
}
325+
256326
.mini-btn {
257327
border: 1px solid var(--panel-border);
258328
background: rgba(255, 255, 255, 0.05);

src/components/TemplateWorkbench.vue

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ const activeStructure = computed(() =>
192192
store.getKnownStructureById(store.inspectedKnownStructureId ?? store.activeKnownStructureId));
193193
const activeTemplate = computed(() =>
194194
store.templateCatalog.find((template) => template.type === store.activeTemplateType) ?? null);
195+
const deleteRunSettings = computed(() => store.templateDrafts['delete-structure-matches'] ?? {});
195196
const activeMatchCount = computed(() =>
196197
activeStructure.value ? store.getKnownStructureMatches(activeStructure.value.id).length : 0);
197198
const hasBuiltInTransform = computed(() =>
@@ -368,6 +369,35 @@ async function copyTransformExample() {
368369
<p class="template-help-line">{{ activeTemplateDescription }}</p>
369370
</template>
370371
</div>
372+
373+
<div
374+
v-if="store.activeTemplateType === 'delete-structure-matches'"
375+
class="run-controls template-run-controls"
376+
>
377+
<label class="run-field">
378+
<span>Run mode</span>
379+
<select
380+
class="panel-input"
381+
:value="deleteRunSettings.runMode || 'until-stable'"
382+
@change="store.updateTemplateDraft('delete-structure-matches', 'runMode', $event.target.value)"
383+
>
384+
<option value="once">Run 1 time</option>
385+
<option value="count">Run X times</option>
386+
<option value="until-stable">Run until no longer effective</option>
387+
</select>
388+
</label>
389+
<label v-if="deleteRunSettings.runMode === 'count'" class="run-field run-field-count">
390+
<span>Iterations</span>
391+
<input
392+
class="panel-input"
393+
type="number"
394+
min="1"
395+
step="1"
396+
:value="deleteRunSettings.maxIterations || 3"
397+
@input="store.updateTemplateDraft('delete-structure-matches', 'maxIterations', $event.target.value)"
398+
>
399+
</label>
400+
</div>
371401
</div>
372402
373403
<div
@@ -473,6 +503,29 @@ async function copyTransformExample() {
473503
gap: 0.5rem;
474504
}
475505
506+
.run-controls {
507+
display: flex;
508+
gap: 0.7rem;
509+
flex-wrap: wrap;
510+
}
511+
512+
.template-run-controls {
513+
margin-top: 0.25rem;
514+
padding-left: 0.1rem;
515+
}
516+
517+
.run-field {
518+
display: flex;
519+
flex-direction: column;
520+
gap: 0.35rem;
521+
min-width: min(18rem, 100%);
522+
color: var(--text-muted);
523+
}
524+
525+
.run-field-count {
526+
min-width: 8rem;
527+
}
528+
476529
.template-help-line {
477530
margin: 0;
478531
}

0 commit comments

Comments
 (0)