@herb-tools/linter 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/herb-lint.js +1525 -98
- package/dist/herb-lint.js.map +1 -1
- package/dist/index.cjs +546 -87
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +465 -87
- package/dist/index.js.map +1 -1
- package/dist/lint-worker.js +1523 -96
- package/dist/lint-worker.js.map +1 -1
- package/dist/loader.cjs +1078 -94
- package/dist/loader.cjs.map +1 -1
- package/dist/loader.js +1057 -95
- package/dist/loader.js.map +1 -1
- package/dist/rules/actionview-no-silent-render.js +31 -0
- package/dist/rules/actionview-no-silent-render.js.map +1 -0
- package/dist/rules/erb-no-case-node-children.js +3 -1
- package/dist/rules/erb-no-case-node-children.js.map +1 -1
- package/dist/rules/erb-no-duplicate-branch-elements.js +95 -11
- package/dist/rules/erb-no-duplicate-branch-elements.js.map +1 -1
- package/dist/rules/erb-no-empty-control-flow.js +190 -0
- package/dist/rules/erb-no-empty-control-flow.js.map +1 -0
- package/dist/rules/erb-no-silent-statement.js +44 -0
- package/dist/rules/erb-no-silent-statement.js.map +1 -0
- package/dist/rules/erb-no-unsafe-script-interpolation.js +37 -3
- package/dist/rules/erb-no-unsafe-script-interpolation.js.map +1 -1
- package/dist/rules/html-allowed-script-type.js +1 -1
- package/dist/rules/html-allowed-script-type.js.map +1 -1
- package/dist/rules/index.js +20 -16
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/rule-utils.js +14 -23
- package/dist/rules/rule-utils.js.map +1 -1
- package/dist/rules.js +8 -2
- package/dist/rules.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/rules/actionview-no-silent-render.d.ts +9 -0
- package/dist/types/rules/erb-no-duplicate-branch-elements.d.ts +1 -0
- package/dist/types/rules/erb-no-empty-control-flow.d.ts +8 -0
- package/dist/types/rules/erb-no-silent-statement.d.ts +9 -0
- package/dist/types/rules/erb-no-unsafe-script-interpolation.d.ts +2 -1
- package/dist/types/rules/index.d.ts +20 -16
- package/dist/types/rules/rule-utils.d.ts +8 -11
- package/dist/types/types.d.ts +4 -3
- package/dist/types.js +6 -3
- package/dist/types.js.map +1 -1
- package/docs/rules/README.md +3 -0
- package/docs/rules/actionview-no-silent-render.md +47 -0
- package/docs/rules/erb-no-empty-control-flow.md +83 -0
- package/docs/rules/erb-no-silent-statement.md +53 -0
- package/docs/rules/erb-no-unsafe-script-interpolation.md +70 -3
- package/package.json +8 -8
- package/src/index.ts +21 -0
- package/src/rules/actionview-no-silent-render.ts +44 -0
- package/src/rules/erb-no-case-node-children.ts +3 -1
- package/src/rules/erb-no-duplicate-branch-elements.ts +130 -14
- package/src/rules/erb-no-empty-control-flow.ts +255 -0
- package/src/rules/erb-no-silent-statement.ts +58 -0
- package/src/rules/erb-no-unsafe-script-interpolation.ts +51 -5
- package/src/rules/html-allowed-script-type.ts +1 -1
- package/src/rules/index.ts +21 -16
- package/src/rules/rule-utils.ts +15 -24
- package/src/rules.ts +8 -2
- package/src/types.ts +7 -3
package/dist/loader.cjs
CHANGED
|
@@ -2327,7 +2327,7 @@ class Token {
|
|
|
2327
2327
|
}
|
|
2328
2328
|
|
|
2329
2329
|
// NOTE: This file is generated by the templates/template.rb script and should not
|
|
2330
|
-
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.
|
|
2330
|
+
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.2/templates/javascript/packages/core/src/errors.ts.erb
|
|
2331
2331
|
class HerbError {
|
|
2332
2332
|
type;
|
|
2333
2333
|
message;
|
|
@@ -3328,6 +3328,279 @@ class NestedERBTagError extends HerbError {
|
|
|
3328
3328
|
return output;
|
|
3329
3329
|
}
|
|
3330
3330
|
}
|
|
3331
|
+
class RenderAmbiguousLocalsError extends HerbError {
|
|
3332
|
+
partial;
|
|
3333
|
+
static from(data) {
|
|
3334
|
+
return new RenderAmbiguousLocalsError({
|
|
3335
|
+
type: data.type,
|
|
3336
|
+
message: data.message,
|
|
3337
|
+
location: Location.from(data.location),
|
|
3338
|
+
partial: data.partial,
|
|
3339
|
+
});
|
|
3340
|
+
}
|
|
3341
|
+
constructor(props) {
|
|
3342
|
+
super(props.type, props.message, props.location);
|
|
3343
|
+
this.partial = props.partial;
|
|
3344
|
+
}
|
|
3345
|
+
toJSON() {
|
|
3346
|
+
return {
|
|
3347
|
+
...super.toJSON(),
|
|
3348
|
+
type: "RENDER_AMBIGUOUS_LOCALS_ERROR",
|
|
3349
|
+
partial: this.partial,
|
|
3350
|
+
};
|
|
3351
|
+
}
|
|
3352
|
+
toMonacoDiagnostic() {
|
|
3353
|
+
return {
|
|
3354
|
+
line: this.location.start.line,
|
|
3355
|
+
column: this.location.start.column,
|
|
3356
|
+
endLine: this.location.end.line,
|
|
3357
|
+
endColumn: this.location.end.column,
|
|
3358
|
+
message: this.message,
|
|
3359
|
+
severity: 'error'
|
|
3360
|
+
};
|
|
3361
|
+
}
|
|
3362
|
+
treeInspect() {
|
|
3363
|
+
let output = "";
|
|
3364
|
+
output += `@ RenderAmbiguousLocalsError ${this.location.treeInspectWithLabel()}\n`;
|
|
3365
|
+
output += `├── message: "${this.message}"\n`;
|
|
3366
|
+
output += `└── partial: ${JSON.stringify(this.partial)}\n`;
|
|
3367
|
+
return output;
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
class RenderMissingLocalsError extends HerbError {
|
|
3371
|
+
partial;
|
|
3372
|
+
keywords;
|
|
3373
|
+
static from(data) {
|
|
3374
|
+
return new RenderMissingLocalsError({
|
|
3375
|
+
type: data.type,
|
|
3376
|
+
message: data.message,
|
|
3377
|
+
location: Location.from(data.location),
|
|
3378
|
+
partial: data.partial,
|
|
3379
|
+
keywords: data.keywords,
|
|
3380
|
+
});
|
|
3381
|
+
}
|
|
3382
|
+
constructor(props) {
|
|
3383
|
+
super(props.type, props.message, props.location);
|
|
3384
|
+
this.partial = props.partial;
|
|
3385
|
+
this.keywords = props.keywords;
|
|
3386
|
+
}
|
|
3387
|
+
toJSON() {
|
|
3388
|
+
return {
|
|
3389
|
+
...super.toJSON(),
|
|
3390
|
+
type: "RENDER_MISSING_LOCALS_ERROR",
|
|
3391
|
+
partial: this.partial,
|
|
3392
|
+
keywords: this.keywords,
|
|
3393
|
+
};
|
|
3394
|
+
}
|
|
3395
|
+
toMonacoDiagnostic() {
|
|
3396
|
+
return {
|
|
3397
|
+
line: this.location.start.line,
|
|
3398
|
+
column: this.location.start.column,
|
|
3399
|
+
endLine: this.location.end.line,
|
|
3400
|
+
endColumn: this.location.end.column,
|
|
3401
|
+
message: this.message,
|
|
3402
|
+
severity: 'error'
|
|
3403
|
+
};
|
|
3404
|
+
}
|
|
3405
|
+
treeInspect() {
|
|
3406
|
+
let output = "";
|
|
3407
|
+
output += `@ RenderMissingLocalsError ${this.location.treeInspectWithLabel()}\n`;
|
|
3408
|
+
output += `├── message: "${this.message}"\n`;
|
|
3409
|
+
output += `├── partial: ${JSON.stringify(this.partial)}\n`;
|
|
3410
|
+
output += `└── keywords: ${JSON.stringify(this.keywords)}\n`;
|
|
3411
|
+
return output;
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
class RenderNoArgumentsError extends HerbError {
|
|
3415
|
+
static from(data) {
|
|
3416
|
+
return new RenderNoArgumentsError({
|
|
3417
|
+
type: data.type,
|
|
3418
|
+
message: data.message,
|
|
3419
|
+
location: Location.from(data.location),
|
|
3420
|
+
});
|
|
3421
|
+
}
|
|
3422
|
+
constructor(props) {
|
|
3423
|
+
super(props.type, props.message, props.location);
|
|
3424
|
+
}
|
|
3425
|
+
toJSON() {
|
|
3426
|
+
return {
|
|
3427
|
+
...super.toJSON(),
|
|
3428
|
+
type: "RENDER_NO_ARGUMENTS_ERROR",
|
|
3429
|
+
};
|
|
3430
|
+
}
|
|
3431
|
+
toMonacoDiagnostic() {
|
|
3432
|
+
return {
|
|
3433
|
+
line: this.location.start.line,
|
|
3434
|
+
column: this.location.start.column,
|
|
3435
|
+
endLine: this.location.end.line,
|
|
3436
|
+
endColumn: this.location.end.column,
|
|
3437
|
+
message: this.message,
|
|
3438
|
+
severity: 'error'
|
|
3439
|
+
};
|
|
3440
|
+
}
|
|
3441
|
+
treeInspect() {
|
|
3442
|
+
let output = "";
|
|
3443
|
+
output += `@ RenderNoArgumentsError ${this.location.treeInspectWithLabel()}\n`;
|
|
3444
|
+
output += `└── message: "${this.message}"\n`;
|
|
3445
|
+
return output;
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
class RenderConflictingPartialError extends HerbError {
|
|
3449
|
+
positional_partial;
|
|
3450
|
+
keyword_partial;
|
|
3451
|
+
static from(data) {
|
|
3452
|
+
return new RenderConflictingPartialError({
|
|
3453
|
+
type: data.type,
|
|
3454
|
+
message: data.message,
|
|
3455
|
+
location: Location.from(data.location),
|
|
3456
|
+
positional_partial: data.positional_partial,
|
|
3457
|
+
keyword_partial: data.keyword_partial,
|
|
3458
|
+
});
|
|
3459
|
+
}
|
|
3460
|
+
constructor(props) {
|
|
3461
|
+
super(props.type, props.message, props.location);
|
|
3462
|
+
this.positional_partial = props.positional_partial;
|
|
3463
|
+
this.keyword_partial = props.keyword_partial;
|
|
3464
|
+
}
|
|
3465
|
+
toJSON() {
|
|
3466
|
+
return {
|
|
3467
|
+
...super.toJSON(),
|
|
3468
|
+
type: "RENDER_CONFLICTING_PARTIAL_ERROR",
|
|
3469
|
+
positional_partial: this.positional_partial,
|
|
3470
|
+
keyword_partial: this.keyword_partial,
|
|
3471
|
+
};
|
|
3472
|
+
}
|
|
3473
|
+
toMonacoDiagnostic() {
|
|
3474
|
+
return {
|
|
3475
|
+
line: this.location.start.line,
|
|
3476
|
+
column: this.location.start.column,
|
|
3477
|
+
endLine: this.location.end.line,
|
|
3478
|
+
endColumn: this.location.end.column,
|
|
3479
|
+
message: this.message,
|
|
3480
|
+
severity: 'error'
|
|
3481
|
+
};
|
|
3482
|
+
}
|
|
3483
|
+
treeInspect() {
|
|
3484
|
+
let output = "";
|
|
3485
|
+
output += `@ RenderConflictingPartialError ${this.location.treeInspectWithLabel()}\n`;
|
|
3486
|
+
output += `├── message: "${this.message}"\n`;
|
|
3487
|
+
output += `├── positional_partial: ${JSON.stringify(this.positional_partial)}\n`;
|
|
3488
|
+
output += `└── keyword_partial: ${JSON.stringify(this.keyword_partial)}\n`;
|
|
3489
|
+
return output;
|
|
3490
|
+
}
|
|
3491
|
+
}
|
|
3492
|
+
class RenderInvalidAsOptionError extends HerbError {
|
|
3493
|
+
as_value;
|
|
3494
|
+
static from(data) {
|
|
3495
|
+
return new RenderInvalidAsOptionError({
|
|
3496
|
+
type: data.type,
|
|
3497
|
+
message: data.message,
|
|
3498
|
+
location: Location.from(data.location),
|
|
3499
|
+
as_value: data.as_value,
|
|
3500
|
+
});
|
|
3501
|
+
}
|
|
3502
|
+
constructor(props) {
|
|
3503
|
+
super(props.type, props.message, props.location);
|
|
3504
|
+
this.as_value = props.as_value;
|
|
3505
|
+
}
|
|
3506
|
+
toJSON() {
|
|
3507
|
+
return {
|
|
3508
|
+
...super.toJSON(),
|
|
3509
|
+
type: "RENDER_INVALID_AS_OPTION_ERROR",
|
|
3510
|
+
as_value: this.as_value,
|
|
3511
|
+
};
|
|
3512
|
+
}
|
|
3513
|
+
toMonacoDiagnostic() {
|
|
3514
|
+
return {
|
|
3515
|
+
line: this.location.start.line,
|
|
3516
|
+
column: this.location.start.column,
|
|
3517
|
+
endLine: this.location.end.line,
|
|
3518
|
+
endColumn: this.location.end.column,
|
|
3519
|
+
message: this.message,
|
|
3520
|
+
severity: 'error'
|
|
3521
|
+
};
|
|
3522
|
+
}
|
|
3523
|
+
treeInspect() {
|
|
3524
|
+
let output = "";
|
|
3525
|
+
output += `@ RenderInvalidAsOptionError ${this.location.treeInspectWithLabel()}\n`;
|
|
3526
|
+
output += `├── message: "${this.message}"\n`;
|
|
3527
|
+
output += `└── as_value: ${JSON.stringify(this.as_value)}\n`;
|
|
3528
|
+
return output;
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
class RenderObjectAndCollectionError extends HerbError {
|
|
3532
|
+
static from(data) {
|
|
3533
|
+
return new RenderObjectAndCollectionError({
|
|
3534
|
+
type: data.type,
|
|
3535
|
+
message: data.message,
|
|
3536
|
+
location: Location.from(data.location),
|
|
3537
|
+
});
|
|
3538
|
+
}
|
|
3539
|
+
constructor(props) {
|
|
3540
|
+
super(props.type, props.message, props.location);
|
|
3541
|
+
}
|
|
3542
|
+
toJSON() {
|
|
3543
|
+
return {
|
|
3544
|
+
...super.toJSON(),
|
|
3545
|
+
type: "RENDER_OBJECT_AND_COLLECTION_ERROR",
|
|
3546
|
+
};
|
|
3547
|
+
}
|
|
3548
|
+
toMonacoDiagnostic() {
|
|
3549
|
+
return {
|
|
3550
|
+
line: this.location.start.line,
|
|
3551
|
+
column: this.location.start.column,
|
|
3552
|
+
endLine: this.location.end.line,
|
|
3553
|
+
endColumn: this.location.end.column,
|
|
3554
|
+
message: this.message,
|
|
3555
|
+
severity: 'error'
|
|
3556
|
+
};
|
|
3557
|
+
}
|
|
3558
|
+
treeInspect() {
|
|
3559
|
+
let output = "";
|
|
3560
|
+
output += `@ RenderObjectAndCollectionError ${this.location.treeInspectWithLabel()}\n`;
|
|
3561
|
+
output += `└── message: "${this.message}"\n`;
|
|
3562
|
+
return output;
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
class RenderLayoutWithoutBlockError extends HerbError {
|
|
3566
|
+
layout;
|
|
3567
|
+
static from(data) {
|
|
3568
|
+
return new RenderLayoutWithoutBlockError({
|
|
3569
|
+
type: data.type,
|
|
3570
|
+
message: data.message,
|
|
3571
|
+
location: Location.from(data.location),
|
|
3572
|
+
layout: data.layout,
|
|
3573
|
+
});
|
|
3574
|
+
}
|
|
3575
|
+
constructor(props) {
|
|
3576
|
+
super(props.type, props.message, props.location);
|
|
3577
|
+
this.layout = props.layout;
|
|
3578
|
+
}
|
|
3579
|
+
toJSON() {
|
|
3580
|
+
return {
|
|
3581
|
+
...super.toJSON(),
|
|
3582
|
+
type: "RENDER_LAYOUT_WITHOUT_BLOCK_ERROR",
|
|
3583
|
+
layout: this.layout,
|
|
3584
|
+
};
|
|
3585
|
+
}
|
|
3586
|
+
toMonacoDiagnostic() {
|
|
3587
|
+
return {
|
|
3588
|
+
line: this.location.start.line,
|
|
3589
|
+
column: this.location.start.column,
|
|
3590
|
+
endLine: this.location.end.line,
|
|
3591
|
+
endColumn: this.location.end.column,
|
|
3592
|
+
message: this.message,
|
|
3593
|
+
severity: 'error'
|
|
3594
|
+
};
|
|
3595
|
+
}
|
|
3596
|
+
treeInspect() {
|
|
3597
|
+
let output = "";
|
|
3598
|
+
output += `@ RenderLayoutWithoutBlockError ${this.location.treeInspectWithLabel()}\n`;
|
|
3599
|
+
output += `├── message: "${this.message}"\n`;
|
|
3600
|
+
output += `└── layout: ${JSON.stringify(this.layout)}\n`;
|
|
3601
|
+
return output;
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3331
3604
|
function fromSerializedError(error) {
|
|
3332
3605
|
switch (error.type) {
|
|
3333
3606
|
case "UNEXPECTED_ERROR": return UnexpectedError.from(error);
|
|
@@ -3353,6 +3626,13 @@ function fromSerializedError(error) {
|
|
|
3353
3626
|
case "UNCLOSED_ERB_TAG_ERROR": return UnclosedERBTagError.from(error);
|
|
3354
3627
|
case "STRAY_ERB_CLOSING_TAG_ERROR": return StrayERBClosingTagError.from(error);
|
|
3355
3628
|
case "NESTED_ERB_TAG_ERROR": return NestedERBTagError.from(error);
|
|
3629
|
+
case "RENDER_AMBIGUOUS_LOCALS_ERROR": return RenderAmbiguousLocalsError.from(error);
|
|
3630
|
+
case "RENDER_MISSING_LOCALS_ERROR": return RenderMissingLocalsError.from(error);
|
|
3631
|
+
case "RENDER_NO_ARGUMENTS_ERROR": return RenderNoArgumentsError.from(error);
|
|
3632
|
+
case "RENDER_CONFLICTING_PARTIAL_ERROR": return RenderConflictingPartialError.from(error);
|
|
3633
|
+
case "RENDER_INVALID_AS_OPTION_ERROR": return RenderInvalidAsOptionError.from(error);
|
|
3634
|
+
case "RENDER_OBJECT_AND_COLLECTION_ERROR": return RenderObjectAndCollectionError.from(error);
|
|
3635
|
+
case "RENDER_LAYOUT_WITHOUT_BLOCK_ERROR": return RenderLayoutWithoutBlockError.from(error);
|
|
3356
3636
|
default:
|
|
3357
3637
|
throw new Error(`Unknown node type: ${error.type}`);
|
|
3358
3638
|
}
|
|
@@ -23368,7 +23648,7 @@ function deserializePrismNode(bytes, source) {
|
|
|
23368
23648
|
}
|
|
23369
23649
|
|
|
23370
23650
|
// NOTE: This file is generated by the templates/template.rb script and should not
|
|
23371
|
-
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.
|
|
23651
|
+
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.2/templates/javascript/packages/core/src/nodes.ts.erb
|
|
23372
23652
|
class Node {
|
|
23373
23653
|
type;
|
|
23374
23654
|
location;
|
|
@@ -25912,6 +26192,225 @@ class ERBUnlessNode extends Node {
|
|
|
25912
26192
|
return output;
|
|
25913
26193
|
}
|
|
25914
26194
|
}
|
|
26195
|
+
class RubyRenderLocalNode extends Node {
|
|
26196
|
+
name;
|
|
26197
|
+
value;
|
|
26198
|
+
static get type() {
|
|
26199
|
+
return "AST_RUBY_RENDER_LOCAL_NODE";
|
|
26200
|
+
}
|
|
26201
|
+
static from(data) {
|
|
26202
|
+
return new RubyRenderLocalNode({
|
|
26203
|
+
type: data.type,
|
|
26204
|
+
location: Location.from(data.location),
|
|
26205
|
+
errors: (data.errors || []).map(error => HerbError.from(error)),
|
|
26206
|
+
name: data.name ? Token.from(data.name) : null,
|
|
26207
|
+
value: data.value ? fromSerializedNode((data.value)) : null,
|
|
26208
|
+
});
|
|
26209
|
+
}
|
|
26210
|
+
constructor(props) {
|
|
26211
|
+
super(props.type, props.location, props.errors);
|
|
26212
|
+
this.name = props.name;
|
|
26213
|
+
this.value = props.value;
|
|
26214
|
+
}
|
|
26215
|
+
accept(visitor) {
|
|
26216
|
+
visitor.visitRubyRenderLocalNode(this);
|
|
26217
|
+
}
|
|
26218
|
+
childNodes() {
|
|
26219
|
+
return [
|
|
26220
|
+
this.value,
|
|
26221
|
+
];
|
|
26222
|
+
}
|
|
26223
|
+
compactChildNodes() {
|
|
26224
|
+
return this.childNodes().filter(node => node !== null && node !== undefined);
|
|
26225
|
+
}
|
|
26226
|
+
recursiveErrors() {
|
|
26227
|
+
return [
|
|
26228
|
+
...this.errors,
|
|
26229
|
+
this.value ? this.value.recursiveErrors() : [],
|
|
26230
|
+
].flat();
|
|
26231
|
+
}
|
|
26232
|
+
toJSON() {
|
|
26233
|
+
return {
|
|
26234
|
+
...super.toJSON(),
|
|
26235
|
+
type: "AST_RUBY_RENDER_LOCAL_NODE",
|
|
26236
|
+
name: this.name ? this.name.toJSON() : null,
|
|
26237
|
+
value: this.value ? this.value.toJSON() : null,
|
|
26238
|
+
};
|
|
26239
|
+
}
|
|
26240
|
+
treeInspect() {
|
|
26241
|
+
let output = "";
|
|
26242
|
+
output += `@ RubyRenderLocalNode ${this.location.treeInspectWithLabel()}\n`;
|
|
26243
|
+
output += `├── errors: ${this.inspectArray(this.errors, "│ ")}`;
|
|
26244
|
+
output += `├── name: ${this.name ? this.name.treeInspect() : "∅"}\n`;
|
|
26245
|
+
output += `└── value: ${this.inspectNode(this.value, " ")}`;
|
|
26246
|
+
return output;
|
|
26247
|
+
}
|
|
26248
|
+
}
|
|
26249
|
+
class ERBRenderNode extends Node {
|
|
26250
|
+
tag_opening;
|
|
26251
|
+
content;
|
|
26252
|
+
tag_closing;
|
|
26253
|
+
// no-op for analyzed_ruby
|
|
26254
|
+
prism_node;
|
|
26255
|
+
partial;
|
|
26256
|
+
template_path;
|
|
26257
|
+
layout;
|
|
26258
|
+
file;
|
|
26259
|
+
inline_template;
|
|
26260
|
+
body;
|
|
26261
|
+
plain;
|
|
26262
|
+
html;
|
|
26263
|
+
renderable;
|
|
26264
|
+
collection;
|
|
26265
|
+
object;
|
|
26266
|
+
as_name;
|
|
26267
|
+
spacer_template;
|
|
26268
|
+
formats;
|
|
26269
|
+
variants;
|
|
26270
|
+
handlers;
|
|
26271
|
+
content_type;
|
|
26272
|
+
locals;
|
|
26273
|
+
static get type() {
|
|
26274
|
+
return "AST_ERB_RENDER_NODE";
|
|
26275
|
+
}
|
|
26276
|
+
static from(data) {
|
|
26277
|
+
return new ERBRenderNode({
|
|
26278
|
+
type: data.type,
|
|
26279
|
+
location: Location.from(data.location),
|
|
26280
|
+
errors: (data.errors || []).map(error => HerbError.from(error)),
|
|
26281
|
+
tag_opening: data.tag_opening ? Token.from(data.tag_opening) : null,
|
|
26282
|
+
content: data.content ? Token.from(data.content) : null,
|
|
26283
|
+
tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
|
|
26284
|
+
// no-op for analyzed_ruby
|
|
26285
|
+
prism_node: data.prism_node ? new Uint8Array(data.prism_node) : null,
|
|
26286
|
+
partial: data.partial ? Token.from(data.partial) : null,
|
|
26287
|
+
template_path: data.template_path ? Token.from(data.template_path) : null,
|
|
26288
|
+
layout: data.layout ? Token.from(data.layout) : null,
|
|
26289
|
+
file: data.file ? Token.from(data.file) : null,
|
|
26290
|
+
inline_template: data.inline_template ? Token.from(data.inline_template) : null,
|
|
26291
|
+
body: data.body ? Token.from(data.body) : null,
|
|
26292
|
+
plain: data.plain ? Token.from(data.plain) : null,
|
|
26293
|
+
html: data.html ? Token.from(data.html) : null,
|
|
26294
|
+
renderable: data.renderable ? Token.from(data.renderable) : null,
|
|
26295
|
+
collection: data.collection ? Token.from(data.collection) : null,
|
|
26296
|
+
object: data.object ? Token.from(data.object) : null,
|
|
26297
|
+
as_name: data.as_name ? Token.from(data.as_name) : null,
|
|
26298
|
+
spacer_template: data.spacer_template ? Token.from(data.spacer_template) : null,
|
|
26299
|
+
formats: data.formats ? Token.from(data.formats) : null,
|
|
26300
|
+
variants: data.variants ? Token.from(data.variants) : null,
|
|
26301
|
+
handlers: data.handlers ? Token.from(data.handlers) : null,
|
|
26302
|
+
content_type: data.content_type ? Token.from(data.content_type) : null,
|
|
26303
|
+
locals: (data.locals || []).map(node => fromSerializedNode(node)),
|
|
26304
|
+
});
|
|
26305
|
+
}
|
|
26306
|
+
constructor(props) {
|
|
26307
|
+
super(props.type, props.location, props.errors);
|
|
26308
|
+
this.tag_opening = props.tag_opening;
|
|
26309
|
+
this.content = props.content;
|
|
26310
|
+
this.tag_closing = props.tag_closing;
|
|
26311
|
+
// no-op for analyzed_ruby
|
|
26312
|
+
this.prism_node = props.prism_node;
|
|
26313
|
+
this.partial = props.partial;
|
|
26314
|
+
this.template_path = props.template_path;
|
|
26315
|
+
this.layout = props.layout;
|
|
26316
|
+
this.file = props.file;
|
|
26317
|
+
this.inline_template = props.inline_template;
|
|
26318
|
+
this.body = props.body;
|
|
26319
|
+
this.plain = props.plain;
|
|
26320
|
+
this.html = props.html;
|
|
26321
|
+
this.renderable = props.renderable;
|
|
26322
|
+
this.collection = props.collection;
|
|
26323
|
+
this.object = props.object;
|
|
26324
|
+
this.as_name = props.as_name;
|
|
26325
|
+
this.spacer_template = props.spacer_template;
|
|
26326
|
+
this.formats = props.formats;
|
|
26327
|
+
this.variants = props.variants;
|
|
26328
|
+
this.handlers = props.handlers;
|
|
26329
|
+
this.content_type = props.content_type;
|
|
26330
|
+
this.locals = props.locals;
|
|
26331
|
+
}
|
|
26332
|
+
accept(visitor) {
|
|
26333
|
+
visitor.visitERBRenderNode(this);
|
|
26334
|
+
}
|
|
26335
|
+
childNodes() {
|
|
26336
|
+
return [
|
|
26337
|
+
...this.locals,
|
|
26338
|
+
];
|
|
26339
|
+
}
|
|
26340
|
+
compactChildNodes() {
|
|
26341
|
+
return this.childNodes().filter(node => node !== null && node !== undefined);
|
|
26342
|
+
}
|
|
26343
|
+
get prismNode() {
|
|
26344
|
+
if (!this.prism_node || !this.source)
|
|
26345
|
+
return null;
|
|
26346
|
+
return deserializePrismNode(this.prism_node, this.source);
|
|
26347
|
+
}
|
|
26348
|
+
recursiveErrors() {
|
|
26349
|
+
return [
|
|
26350
|
+
...this.errors,
|
|
26351
|
+
...this.locals.map(node => node.recursiveErrors()),
|
|
26352
|
+
].flat();
|
|
26353
|
+
}
|
|
26354
|
+
toJSON() {
|
|
26355
|
+
return {
|
|
26356
|
+
...super.toJSON(),
|
|
26357
|
+
type: "AST_ERB_RENDER_NODE",
|
|
26358
|
+
tag_opening: this.tag_opening ? this.tag_opening.toJSON() : null,
|
|
26359
|
+
content: this.content ? this.content.toJSON() : null,
|
|
26360
|
+
tag_closing: this.tag_closing ? this.tag_closing.toJSON() : null,
|
|
26361
|
+
// no-op for analyzed_ruby
|
|
26362
|
+
prism_node: this.prism_node ? Array.from(this.prism_node) : null,
|
|
26363
|
+
partial: this.partial ? this.partial.toJSON() : null,
|
|
26364
|
+
template_path: this.template_path ? this.template_path.toJSON() : null,
|
|
26365
|
+
layout: this.layout ? this.layout.toJSON() : null,
|
|
26366
|
+
file: this.file ? this.file.toJSON() : null,
|
|
26367
|
+
inline_template: this.inline_template ? this.inline_template.toJSON() : null,
|
|
26368
|
+
body: this.body ? this.body.toJSON() : null,
|
|
26369
|
+
plain: this.plain ? this.plain.toJSON() : null,
|
|
26370
|
+
html: this.html ? this.html.toJSON() : null,
|
|
26371
|
+
renderable: this.renderable ? this.renderable.toJSON() : null,
|
|
26372
|
+
collection: this.collection ? this.collection.toJSON() : null,
|
|
26373
|
+
object: this.object ? this.object.toJSON() : null,
|
|
26374
|
+
as_name: this.as_name ? this.as_name.toJSON() : null,
|
|
26375
|
+
spacer_template: this.spacer_template ? this.spacer_template.toJSON() : null,
|
|
26376
|
+
formats: this.formats ? this.formats.toJSON() : null,
|
|
26377
|
+
variants: this.variants ? this.variants.toJSON() : null,
|
|
26378
|
+
handlers: this.handlers ? this.handlers.toJSON() : null,
|
|
26379
|
+
content_type: this.content_type ? this.content_type.toJSON() : null,
|
|
26380
|
+
locals: this.locals.map(node => node.toJSON()),
|
|
26381
|
+
};
|
|
26382
|
+
}
|
|
26383
|
+
treeInspect() {
|
|
26384
|
+
let output = "";
|
|
26385
|
+
output += `@ ERBRenderNode ${this.location.treeInspectWithLabel()}\n`;
|
|
26386
|
+
output += `├── errors: ${this.inspectArray(this.errors, "│ ")}`;
|
|
26387
|
+
output += `├── tag_opening: ${this.tag_opening ? this.tag_opening.treeInspect() : "∅"}\n`;
|
|
26388
|
+
output += `├── content: ${this.content ? this.content.treeInspect() : "∅"}\n`;
|
|
26389
|
+
output += `├── tag_closing: ${this.tag_closing ? this.tag_closing.treeInspect() : "∅"}\n`;
|
|
26390
|
+
if (this.prism_node) {
|
|
26391
|
+
output += `├── prism_node: ${this.source ? inspectPrismSerialized(this.prism_node, this.source, "│ ") : `(${this.prism_node.length} bytes)`}\n`;
|
|
26392
|
+
}
|
|
26393
|
+
output += `├── partial: ${this.partial ? this.partial.treeInspect() : "∅"}\n`;
|
|
26394
|
+
output += `├── template_path: ${this.template_path ? this.template_path.treeInspect() : "∅"}\n`;
|
|
26395
|
+
output += `├── layout: ${this.layout ? this.layout.treeInspect() : "∅"}\n`;
|
|
26396
|
+
output += `├── file: ${this.file ? this.file.treeInspect() : "∅"}\n`;
|
|
26397
|
+
output += `├── inline_template: ${this.inline_template ? this.inline_template.treeInspect() : "∅"}\n`;
|
|
26398
|
+
output += `├── body: ${this.body ? this.body.treeInspect() : "∅"}\n`;
|
|
26399
|
+
output += `├── plain: ${this.plain ? this.plain.treeInspect() : "∅"}\n`;
|
|
26400
|
+
output += `├── html: ${this.html ? this.html.treeInspect() : "∅"}\n`;
|
|
26401
|
+
output += `├── renderable: ${this.renderable ? this.renderable.treeInspect() : "∅"}\n`;
|
|
26402
|
+
output += `├── collection: ${this.collection ? this.collection.treeInspect() : "∅"}\n`;
|
|
26403
|
+
output += `├── object: ${this.object ? this.object.treeInspect() : "∅"}\n`;
|
|
26404
|
+
output += `├── as_name: ${this.as_name ? this.as_name.treeInspect() : "∅"}\n`;
|
|
26405
|
+
output += `├── spacer_template: ${this.spacer_template ? this.spacer_template.treeInspect() : "∅"}\n`;
|
|
26406
|
+
output += `├── formats: ${this.formats ? this.formats.treeInspect() : "∅"}\n`;
|
|
26407
|
+
output += `├── variants: ${this.variants ? this.variants.treeInspect() : "∅"}\n`;
|
|
26408
|
+
output += `├── handlers: ${this.handlers ? this.handlers.treeInspect() : "∅"}\n`;
|
|
26409
|
+
output += `├── content_type: ${this.content_type ? this.content_type.treeInspect() : "∅"}\n`;
|
|
26410
|
+
output += `└── locals: ${this.inspectArray(this.locals, " ")}`;
|
|
26411
|
+
return output;
|
|
26412
|
+
}
|
|
26413
|
+
}
|
|
25915
26414
|
class ERBYieldNode extends Node {
|
|
25916
26415
|
tag_opening;
|
|
25917
26416
|
content;
|
|
@@ -26075,6 +26574,8 @@ function fromSerializedNode(node) {
|
|
|
26075
26574
|
case "AST_ERB_ENSURE_NODE": return ERBEnsureNode.from(node);
|
|
26076
26575
|
case "AST_ERB_BEGIN_NODE": return ERBBeginNode.from(node);
|
|
26077
26576
|
case "AST_ERB_UNLESS_NODE": return ERBUnlessNode.from(node);
|
|
26577
|
+
case "AST_RUBY_RENDER_LOCAL_NODE": return RubyRenderLocalNode.from(node);
|
|
26578
|
+
case "AST_ERB_RENDER_NODE": return ERBRenderNode.from(node);
|
|
26078
26579
|
case "AST_ERB_YIELD_NODE": return ERBYieldNode.from(node);
|
|
26079
26580
|
case "AST_ERB_IN_NODE": return ERBInNode.from(node);
|
|
26080
26581
|
default:
|
|
@@ -26124,6 +26625,7 @@ const DEFAULT_PARSER_OPTIONS = {
|
|
|
26124
26625
|
analyze: true,
|
|
26125
26626
|
strict: true,
|
|
26126
26627
|
action_view_helpers: false,
|
|
26628
|
+
render_nodes: false,
|
|
26127
26629
|
prism_nodes: false,
|
|
26128
26630
|
prism_nodes_deep: false,
|
|
26129
26631
|
prism_program: false,
|
|
@@ -26140,6 +26642,8 @@ class ParserOptions {
|
|
|
26140
26642
|
analyze;
|
|
26141
26643
|
/** Whether ActionView tag helper transformation was enabled during parsing. */
|
|
26142
26644
|
action_view_helpers;
|
|
26645
|
+
/** Whether ActionView render call detection was enabled during parsing. */
|
|
26646
|
+
render_nodes;
|
|
26143
26647
|
/** Whether Prism node serialization was enabled during parsing. */
|
|
26144
26648
|
prism_nodes;
|
|
26145
26649
|
/** Whether deep Prism node serialization was enabled during parsing. */
|
|
@@ -26154,6 +26658,7 @@ class ParserOptions {
|
|
|
26154
26658
|
this.track_whitespace = options.track_whitespace ?? DEFAULT_PARSER_OPTIONS.track_whitespace;
|
|
26155
26659
|
this.analyze = options.analyze ?? DEFAULT_PARSER_OPTIONS.analyze;
|
|
26156
26660
|
this.action_view_helpers = options.action_view_helpers ?? DEFAULT_PARSER_OPTIONS.action_view_helpers;
|
|
26661
|
+
this.render_nodes = options.render_nodes ?? DEFAULT_PARSER_OPTIONS.render_nodes;
|
|
26157
26662
|
this.prism_nodes = options.prism_nodes ?? DEFAULT_PARSER_OPTIONS.prism_nodes;
|
|
26158
26663
|
this.prism_nodes_deep = options.prism_nodes_deep ?? DEFAULT_PARSER_OPTIONS.prism_nodes_deep;
|
|
26159
26664
|
this.prism_program = options.prism_program ?? DEFAULT_PARSER_OPTIONS.prism_program;
|
|
@@ -26233,7 +26738,7 @@ class ParseResult extends Result {
|
|
|
26233
26738
|
}
|
|
26234
26739
|
|
|
26235
26740
|
// NOTE: This file is generated by the templates/template.rb script and should not
|
|
26236
|
-
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.
|
|
26741
|
+
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.2/templates/javascript/packages/core/src/node-type-guards.ts.erb
|
|
26237
26742
|
/**
|
|
26238
26743
|
* Type guard functions for AST nodes.
|
|
26239
26744
|
* These functions provide type checking by combining both instanceof
|
|
@@ -26528,6 +27033,22 @@ function isERBUnlessNode(node) {
|
|
|
26528
27033
|
return false;
|
|
26529
27034
|
return node instanceof ERBUnlessNode || node.type === "AST_ERB_UNLESS_NODE" || node.constructor.type === "AST_ERB_UNLESS_NODE";
|
|
26530
27035
|
}
|
|
27036
|
+
/**
|
|
27037
|
+
* Checks if a node is a RubyRenderLocalNode
|
|
27038
|
+
*/
|
|
27039
|
+
function isRubyRenderLocalNode(node) {
|
|
27040
|
+
if (!node)
|
|
27041
|
+
return false;
|
|
27042
|
+
return node instanceof RubyRenderLocalNode || node.type === "AST_RUBY_RENDER_LOCAL_NODE" || node.constructor.type === "AST_RUBY_RENDER_LOCAL_NODE";
|
|
27043
|
+
}
|
|
27044
|
+
/**
|
|
27045
|
+
* Checks if a node is a ERBRenderNode
|
|
27046
|
+
*/
|
|
27047
|
+
function isERBRenderNode(node) {
|
|
27048
|
+
if (!node)
|
|
27049
|
+
return false;
|
|
27050
|
+
return node instanceof ERBRenderNode || node.type === "AST_ERB_RENDER_NODE" || node.constructor.type === "AST_ERB_RENDER_NODE";
|
|
27051
|
+
}
|
|
26531
27052
|
/**
|
|
26532
27053
|
* Checks if a node is a ERBYieldNode
|
|
26533
27054
|
*/
|
|
@@ -26564,6 +27085,7 @@ function isERBNode(node) {
|
|
|
26564
27085
|
isERBEnsureNode(node) ||
|
|
26565
27086
|
isERBBeginNode(node) ||
|
|
26566
27087
|
isERBUnlessNode(node) ||
|
|
27088
|
+
isERBRenderNode(node) ||
|
|
26567
27089
|
isERBYieldNode(node) ||
|
|
26568
27090
|
isERBInNode(node);
|
|
26569
27091
|
}
|
|
@@ -26614,6 +27136,8 @@ const NODE_TYPE_GUARDS = new Map([
|
|
|
26614
27136
|
[ERBEnsureNode, isERBEnsureNode],
|
|
26615
27137
|
[ERBBeginNode, isERBBeginNode],
|
|
26616
27138
|
[ERBUnlessNode, isERBUnlessNode],
|
|
27139
|
+
[RubyRenderLocalNode, isRubyRenderLocalNode],
|
|
27140
|
+
[ERBRenderNode, isERBRenderNode],
|
|
26617
27141
|
[ERBYieldNode, isERBYieldNode],
|
|
26618
27142
|
[ERBInNode, isERBInNode],
|
|
26619
27143
|
]);
|
|
@@ -26664,6 +27188,8 @@ const AST_TYPE_GUARDS = new Map([
|
|
|
26664
27188
|
["AST_ERB_ENSURE_NODE", isERBEnsureNode],
|
|
26665
27189
|
["AST_ERB_BEGIN_NODE", isERBBeginNode],
|
|
26666
27190
|
["AST_ERB_UNLESS_NODE", isERBUnlessNode],
|
|
27191
|
+
["AST_RUBY_RENDER_LOCAL_NODE", isRubyRenderLocalNode],
|
|
27192
|
+
["AST_ERB_RENDER_NODE", isERBRenderNode],
|
|
26667
27193
|
["AST_ERB_YIELD_NODE", isERBYieldNode],
|
|
26668
27194
|
["AST_ERB_IN_NODE", isERBInNode],
|
|
26669
27195
|
]);
|
|
@@ -26810,6 +27336,12 @@ function getStaticContentFromNodes(nodes) {
|
|
|
26810
27336
|
}
|
|
26811
27337
|
return literalNodes.map(node => node.content).join("");
|
|
26812
27338
|
}
|
|
27339
|
+
/**
|
|
27340
|
+
* Checks if nodes contain any literal content (for static validation)
|
|
27341
|
+
*/
|
|
27342
|
+
function hasStaticContent(nodes) {
|
|
27343
|
+
return nodes.some(isLiteralNode);
|
|
27344
|
+
}
|
|
26813
27345
|
/**
|
|
26814
27346
|
* Checks if nodes are effectively static (only literals and non-output ERB)
|
|
26815
27347
|
* Non-output ERB like <% if %> doesn't affect static validation
|
|
@@ -26852,7 +27384,7 @@ function getCombinedStringFromNodes(nodes) {
|
|
|
26852
27384
|
/**
|
|
26853
27385
|
* Checks if an HTML attribute name node has dynamic content (contains ERB)
|
|
26854
27386
|
*/
|
|
26855
|
-
function
|
|
27387
|
+
function hasDynamicAttributeNameNode(attributeNameNode) {
|
|
26856
27388
|
if (!attributeNameNode.children) {
|
|
26857
27389
|
return false;
|
|
26858
27390
|
}
|
|
@@ -26926,7 +27458,9 @@ function getAttributeName(attributeNode, lowercase = true) {
|
|
|
26926
27458
|
return staticName ? staticName.toLowerCase() : null;
|
|
26927
27459
|
}
|
|
26928
27460
|
function hasStaticAttributeValue(nodeOrAttribute, attributeName) {
|
|
26929
|
-
const attributeNode =
|
|
27461
|
+
const attributeNode = attributeName
|
|
27462
|
+
? getAttribute(nodeOrAttribute, attributeName)
|
|
27463
|
+
: nodeOrAttribute;
|
|
26930
27464
|
if (!attributeNode?.value?.children)
|
|
26931
27465
|
return false;
|
|
26932
27466
|
return attributeNode.value.children.every(isLiteralNode);
|
|
@@ -26985,12 +27519,11 @@ function hasAttribute(node, attributeName) {
|
|
|
26985
27519
|
}
|
|
26986
27520
|
/**
|
|
26987
27521
|
* Checks if an attribute has a dynamic (ERB-containing) name.
|
|
26988
|
-
* Accepts an HTMLAttributeNode (wraps the core HTMLAttributeNameNode-level check).
|
|
26989
27522
|
*/
|
|
26990
|
-
function
|
|
27523
|
+
function hasDynamicAttributeName(attributeNode) {
|
|
26991
27524
|
if (!isHTMLAttributeNameNode(attributeNode.name))
|
|
26992
27525
|
return false;
|
|
26993
|
-
return
|
|
27526
|
+
return hasDynamicAttributeNameNode(attributeNode.name);
|
|
26994
27527
|
}
|
|
26995
27528
|
/**
|
|
26996
27529
|
* Gets the combined string representation of an attribute name (including ERB syntax).
|
|
@@ -27001,12 +27534,34 @@ function getCombinedAttributeNameString(attributeNode) {
|
|
|
27001
27534
|
return "";
|
|
27002
27535
|
return getCombinedAttributeName(attributeNode.name);
|
|
27003
27536
|
}
|
|
27537
|
+
/**
|
|
27538
|
+
* Checks if an attribute value contains dynamic content (ERB)
|
|
27539
|
+
*/
|
|
27540
|
+
function hasDynamicAttributeValue(attributeNode) {
|
|
27541
|
+
if (!attributeNode.value?.children)
|
|
27542
|
+
return false;
|
|
27543
|
+
return attributeNode.value.children.some(isERBContentNode);
|
|
27544
|
+
}
|
|
27004
27545
|
/**
|
|
27005
27546
|
* Gets the value nodes array from an attribute for dynamic inspection
|
|
27006
27547
|
*/
|
|
27007
27548
|
function getAttributeValueNodes(attributeNode) {
|
|
27008
27549
|
return attributeNode.value?.children || [];
|
|
27009
27550
|
}
|
|
27551
|
+
/**
|
|
27552
|
+
* Checks if an attribute value contains any static content (for validation purposes)
|
|
27553
|
+
*/
|
|
27554
|
+
function hasStaticAttributeValueContent(attributeNode) {
|
|
27555
|
+
return hasStaticContent(getAttributeValueNodes(attributeNode));
|
|
27556
|
+
}
|
|
27557
|
+
/**
|
|
27558
|
+
* Gets the static content of an attribute value (all literal parts combined).
|
|
27559
|
+
* Unlike getStaticAttributeValue, this extracts only the static portions from mixed content.
|
|
27560
|
+
* Returns the concatenated literal content, or null if no literal nodes exist.
|
|
27561
|
+
*/
|
|
27562
|
+
function getStaticAttributeValueContent(attributeNode) {
|
|
27563
|
+
return getStaticContentFromNodes(getAttributeValueNodes(attributeNode));
|
|
27564
|
+
}
|
|
27010
27565
|
/**
|
|
27011
27566
|
* Gets the combined attribute value including both static text and ERB tag syntax.
|
|
27012
27567
|
* For ERB nodes, includes the full tag syntax (e.g., "<%= foo %>").
|
|
@@ -27050,6 +27605,14 @@ function getAttributeValueQuoteType(node) {
|
|
|
27050
27605
|
}
|
|
27051
27606
|
return "none";
|
|
27052
27607
|
}
|
|
27608
|
+
/**
|
|
27609
|
+
* Checks if an attribute value is quoted
|
|
27610
|
+
*/
|
|
27611
|
+
function isAttributeValueQuoted(attributeNode) {
|
|
27612
|
+
if (!isHTMLAttributeValueNode(attributeNode.value))
|
|
27613
|
+
return false;
|
|
27614
|
+
return !!attributeNode.value.quoted;
|
|
27615
|
+
}
|
|
27053
27616
|
/**
|
|
27054
27617
|
* Iterates over all attributes of an element or open tag node
|
|
27055
27618
|
*/
|
|
@@ -27343,6 +27906,19 @@ function createWhitespaceNode() {
|
|
|
27343
27906
|
});
|
|
27344
27907
|
}
|
|
27345
27908
|
|
|
27909
|
+
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#boolean-attributes
|
|
27910
|
+
const HTML_BOOLEAN_ATTRIBUTES = new Set([
|
|
27911
|
+
"allowfullscreen", "async", "autofocus", "autoplay", "checked", "compact",
|
|
27912
|
+
"controls", "declare", "default", "defer", "disabled", "formnovalidate",
|
|
27913
|
+
"hidden", "inert", "ismap", "itemscope", "loop", "multiple", "muted",
|
|
27914
|
+
"nomodule", "nohref", "noresize", "noshade", "novalidate", "nowrap",
|
|
27915
|
+
"open", "playsinline", "readonly", "required", "reversed", "scoped",
|
|
27916
|
+
"seamless", "selected", "sortable", "truespeed", "typemustmatch",
|
|
27917
|
+
]);
|
|
27918
|
+
function isBooleanAttribute(attributeName) {
|
|
27919
|
+
return HTML_BOOLEAN_ATTRIBUTES.has(attributeName.toLowerCase());
|
|
27920
|
+
}
|
|
27921
|
+
|
|
27346
27922
|
/*
|
|
27347
27923
|
* The following code is derived from the "js-levenshtein" repository,
|
|
27348
27924
|
* Copyright (c) 2017 Gustaf Andersson (https://github.com/gustf/js-levenshtein)
|
|
@@ -27489,7 +28065,7 @@ function didyoumean(input, list, threshold) {
|
|
|
27489
28065
|
}
|
|
27490
28066
|
|
|
27491
28067
|
// NOTE: This file is generated by the templates/template.rb script and should not
|
|
27492
|
-
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.
|
|
28068
|
+
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.2/templates/javascript/packages/core/src/visitor.ts.erb
|
|
27493
28069
|
class Visitor {
|
|
27494
28070
|
visit(node) {
|
|
27495
28071
|
if (!node)
|
|
@@ -27668,6 +28244,15 @@ class Visitor {
|
|
|
27668
28244
|
this.visitERBNode(node);
|
|
27669
28245
|
this.visitChildNodes(node);
|
|
27670
28246
|
}
|
|
28247
|
+
visitRubyRenderLocalNode(node) {
|
|
28248
|
+
this.visitNode(node);
|
|
28249
|
+
this.visitChildNodes(node);
|
|
28250
|
+
}
|
|
28251
|
+
visitERBRenderNode(node) {
|
|
28252
|
+
this.visitNode(node);
|
|
28253
|
+
this.visitERBNode(node);
|
|
28254
|
+
this.visitChildNodes(node);
|
|
28255
|
+
}
|
|
27671
28256
|
visitERBYieldNode(node) {
|
|
27672
28257
|
this.visitNode(node);
|
|
27673
28258
|
this.visitERBNode(node);
|
|
@@ -28118,6 +28703,12 @@ class IdentityPrinter extends Printer {
|
|
|
28118
28703
|
this.visit(node.end_node);
|
|
28119
28704
|
}
|
|
28120
28705
|
}
|
|
28706
|
+
visitERBRenderNode(node) {
|
|
28707
|
+
this.printERBNode(node);
|
|
28708
|
+
}
|
|
28709
|
+
visitRubyRenderLocalNode(_node) {
|
|
28710
|
+
// extracted metadata, nothing to print
|
|
28711
|
+
}
|
|
28121
28712
|
visitERBYieldNode(node) {
|
|
28122
28713
|
this.printERBNode(node);
|
|
28123
28714
|
}
|
|
@@ -28668,7 +29259,7 @@ class ParserRule {
|
|
|
28668
29259
|
get parserOptions() {
|
|
28669
29260
|
return DEFAULT_LINTER_PARSER_OPTIONS;
|
|
28670
29261
|
}
|
|
28671
|
-
createOffense(message, location, autofixContext, severity) {
|
|
29262
|
+
createOffense(message, location, autofixContext, severity, tags) {
|
|
28672
29263
|
return {
|
|
28673
29264
|
rule: this.ruleName,
|
|
28674
29265
|
code: this.ruleName,
|
|
@@ -28677,6 +29268,7 @@ class ParserRule {
|
|
|
28677
29268
|
location,
|
|
28678
29269
|
autofixContext,
|
|
28679
29270
|
severity,
|
|
29271
|
+
tags,
|
|
28680
29272
|
};
|
|
28681
29273
|
}
|
|
28682
29274
|
}
|
|
@@ -28696,7 +29288,7 @@ class LexerRule {
|
|
|
28696
29288
|
get defaultConfig() {
|
|
28697
29289
|
return DEFAULT_RULE_CONFIG;
|
|
28698
29290
|
}
|
|
28699
|
-
createOffense(message, location, autofixContext, severity) {
|
|
29291
|
+
createOffense(message, location, autofixContext, severity, tags) {
|
|
28700
29292
|
return {
|
|
28701
29293
|
rule: this.ruleName,
|
|
28702
29294
|
code: this.ruleName,
|
|
@@ -28705,6 +29297,7 @@ class LexerRule {
|
|
|
28705
29297
|
location,
|
|
28706
29298
|
autofixContext,
|
|
28707
29299
|
severity,
|
|
29300
|
+
tags,
|
|
28708
29301
|
};
|
|
28709
29302
|
}
|
|
28710
29303
|
}
|
|
@@ -28730,7 +29323,7 @@ class SourceRule {
|
|
|
28730
29323
|
get defaultConfig() {
|
|
28731
29324
|
return DEFAULT_RULE_CONFIG;
|
|
28732
29325
|
}
|
|
28733
|
-
createOffense(message, location, autofixContext, severity) {
|
|
29326
|
+
createOffense(message, location, autofixContext, severity, tags) {
|
|
28734
29327
|
return {
|
|
28735
29328
|
rule: this.ruleName,
|
|
28736
29329
|
code: this.ruleName,
|
|
@@ -28739,6 +29332,7 @@ class SourceRule {
|
|
|
28739
29332
|
location,
|
|
28740
29333
|
autofixContext,
|
|
28741
29334
|
severity,
|
|
29335
|
+
tags,
|
|
28742
29336
|
};
|
|
28743
29337
|
}
|
|
28744
29338
|
}
|
|
@@ -28764,7 +29358,7 @@ class BaseRuleVisitor extends Visitor {
|
|
|
28764
29358
|
* Helper method to create an unbound lint offense (without severity).
|
|
28765
29359
|
* The Linter will bind severity based on the rule's config.
|
|
28766
29360
|
*/
|
|
28767
|
-
createOffense(message, location, autofixContext, severity) {
|
|
29361
|
+
createOffense(message, location, autofixContext, severity, tags) {
|
|
28768
29362
|
return {
|
|
28769
29363
|
rule: this.ruleName,
|
|
28770
29364
|
code: this.ruleName,
|
|
@@ -28773,13 +29367,14 @@ class BaseRuleVisitor extends Visitor {
|
|
|
28773
29367
|
location,
|
|
28774
29368
|
autofixContext,
|
|
28775
29369
|
severity,
|
|
29370
|
+
tags,
|
|
28776
29371
|
};
|
|
28777
29372
|
}
|
|
28778
29373
|
/**
|
|
28779
29374
|
* Helper method to add an offense to the offenses array
|
|
28780
29375
|
*/
|
|
28781
|
-
addOffense(message, location, autofixContext, severity) {
|
|
28782
|
-
this.offenses.push(this.createOffense(message, location, autofixContext, severity));
|
|
29376
|
+
addOffense(message, location, autofixContext, severity, tags) {
|
|
29377
|
+
this.offenses.push(this.createOffense(message, location, autofixContext, severity, tags));
|
|
28783
29378
|
}
|
|
28784
29379
|
}
|
|
28785
29380
|
/**
|
|
@@ -28866,13 +29461,6 @@ const HTML_VOID_ELEMENTS = new Set([
|
|
|
28866
29461
|
"area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta",
|
|
28867
29462
|
"param", "source", "track", "wbr",
|
|
28868
29463
|
]);
|
|
28869
|
-
const HTML_BOOLEAN_ATTRIBUTES = new Set([
|
|
28870
|
-
"autofocus", "autoplay", "checked", "controls", "defer", "disabled", "hidden",
|
|
28871
|
-
"loop", "multiple", "muted", "readonly", "required", "reversed", "selected",
|
|
28872
|
-
"open", "default", "formnovalidate", "novalidate", "itemscope", "scoped",
|
|
28873
|
-
"seamless", "allowfullscreen", "async", "compact", "declare", "nohref",
|
|
28874
|
-
"noresize", "noshade", "nowrap", "sortable", "truespeed", "typemustmatch"
|
|
28875
|
-
]);
|
|
28876
29464
|
const HEADING_TAGS = new Set(["h1", "h2", "h3", "h4", "h5", "h6"]);
|
|
28877
29465
|
/**
|
|
28878
29466
|
* SVG elements that use camelCase naming
|
|
@@ -29027,12 +29615,6 @@ function isBlockElement(tagName) {
|
|
|
29027
29615
|
function isVoidElement(tagName) {
|
|
29028
29616
|
return HTML_VOID_ELEMENTS.has(tagName.toLowerCase());
|
|
29029
29617
|
}
|
|
29030
|
-
/**
|
|
29031
|
-
* Checks if an attribute is a boolean attribute
|
|
29032
|
-
*/
|
|
29033
|
-
function isBooleanAttribute(attributeName) {
|
|
29034
|
-
return HTML_BOOLEAN_ATTRIBUTES.has(attributeName.toLowerCase());
|
|
29035
|
-
}
|
|
29036
29618
|
/**
|
|
29037
29619
|
* Attribute visitor that provides granular processing based on both
|
|
29038
29620
|
* attribute name type (static/dynamic) and value type (static/dynamic)
|
|
@@ -29055,7 +29637,7 @@ class AttributeVisitorMixin extends BaseRuleVisitor {
|
|
|
29055
29637
|
forEachAttribute(node, (attributeNode) => {
|
|
29056
29638
|
const staticAttributeName = getAttributeName(attributeNode);
|
|
29057
29639
|
const originalAttributeName = getAttributeName(attributeNode, false) || "";
|
|
29058
|
-
const isDynamicName =
|
|
29640
|
+
const isDynamicName = hasDynamicAttributeName(attributeNode);
|
|
29059
29641
|
const staticAttributeValue = getStaticAttributeValue(attributeNode);
|
|
29060
29642
|
const valueNodes = getAttributeValueNodes(attributeNode);
|
|
29061
29643
|
const hasOutputERB = hasERBOutput(valueNodes);
|
|
@@ -29132,7 +29714,7 @@ class BaseLexerRuleVisitor {
|
|
|
29132
29714
|
* Helper method to create an unbound lint offense (without severity).
|
|
29133
29715
|
* The Linter will bind severity based on the rule's config.
|
|
29134
29716
|
*/
|
|
29135
|
-
createOffense(message, location, autofixContext, severity) {
|
|
29717
|
+
createOffense(message, location, autofixContext, severity, tags) {
|
|
29136
29718
|
return {
|
|
29137
29719
|
rule: this.ruleName,
|
|
29138
29720
|
code: this.ruleName,
|
|
@@ -29141,13 +29723,14 @@ class BaseLexerRuleVisitor {
|
|
|
29141
29723
|
location,
|
|
29142
29724
|
autofixContext,
|
|
29143
29725
|
severity,
|
|
29726
|
+
tags,
|
|
29144
29727
|
};
|
|
29145
29728
|
}
|
|
29146
29729
|
/**
|
|
29147
29730
|
* Helper method to add an offense to the offenses array
|
|
29148
29731
|
*/
|
|
29149
|
-
addOffense(message, location, autofixContext, severity) {
|
|
29150
|
-
this.offenses.push(this.createOffense(message, location, autofixContext, severity));
|
|
29732
|
+
addOffense(message, location, autofixContext, severity, tags) {
|
|
29733
|
+
this.offenses.push(this.createOffense(message, location, autofixContext, severity, tags));
|
|
29151
29734
|
}
|
|
29152
29735
|
/**
|
|
29153
29736
|
* Main entry point for lexer rule visitors
|
|
@@ -29188,7 +29771,7 @@ class BaseSourceRuleVisitor {
|
|
|
29188
29771
|
* Helper method to create an unbound lint offense (without severity).
|
|
29189
29772
|
* The Linter will bind severity based on the rule's config.
|
|
29190
29773
|
*/
|
|
29191
|
-
createOffense(message, location, autofixContext, severity) {
|
|
29774
|
+
createOffense(message, location, autofixContext, severity, tags) {
|
|
29192
29775
|
return {
|
|
29193
29776
|
rule: this.ruleName,
|
|
29194
29777
|
code: this.ruleName,
|
|
@@ -29197,13 +29780,14 @@ class BaseSourceRuleVisitor {
|
|
|
29197
29780
|
location,
|
|
29198
29781
|
autofixContext,
|
|
29199
29782
|
severity,
|
|
29783
|
+
tags,
|
|
29200
29784
|
};
|
|
29201
29785
|
}
|
|
29202
29786
|
/**
|
|
29203
29787
|
* Helper method to add an offense to the offenses array
|
|
29204
29788
|
*/
|
|
29205
|
-
addOffense(message, location, autofixContext, severity) {
|
|
29206
|
-
this.offenses.push(this.createOffense(message, location, autofixContext, severity));
|
|
29789
|
+
addOffense(message, location, autofixContext, severity, tags) {
|
|
29790
|
+
this.offenses.push(this.createOffense(message, location, autofixContext, severity, tags));
|
|
29207
29791
|
}
|
|
29208
29792
|
/**
|
|
29209
29793
|
* Main entry point for source rule visitors
|
|
@@ -29553,6 +30137,34 @@ class ActionViewNoSilentHelperRule extends ParserRule {
|
|
|
29553
30137
|
}
|
|
29554
30138
|
}
|
|
29555
30139
|
|
|
30140
|
+
class ActionViewNoSilentRenderVisitor extends BaseRuleVisitor {
|
|
30141
|
+
visitERBRenderNode(node) {
|
|
30142
|
+
if (!isERBOutputNode(node)) {
|
|
30143
|
+
this.addOffense(`Avoid using \`${node.tag_opening?.value} %>\` with \`render\`. Use \`<%= %>\` to ensure the rendered content is output.`, node.location);
|
|
30144
|
+
}
|
|
30145
|
+
this.visitChildNodes(node);
|
|
30146
|
+
}
|
|
30147
|
+
}
|
|
30148
|
+
class ActionViewNoSilentRenderRule extends ParserRule {
|
|
30149
|
+
static ruleName = "actionview-no-silent-render";
|
|
30150
|
+
get defaultConfig() {
|
|
30151
|
+
return {
|
|
30152
|
+
enabled: true,
|
|
30153
|
+
severity: "error"
|
|
30154
|
+
};
|
|
30155
|
+
}
|
|
30156
|
+
get parserOptions() {
|
|
30157
|
+
return {
|
|
30158
|
+
render_nodes: true,
|
|
30159
|
+
};
|
|
30160
|
+
}
|
|
30161
|
+
check(result, context) {
|
|
30162
|
+
const visitor = new ActionViewNoSilentRenderVisitor(this.ruleName, context);
|
|
30163
|
+
visitor.visit(result.value);
|
|
30164
|
+
return visitor.offenses;
|
|
30165
|
+
}
|
|
30166
|
+
}
|
|
30167
|
+
|
|
29556
30168
|
class ERBCommentSyntaxVisitor extends BaseRuleVisitor {
|
|
29557
30169
|
visitERBContentNode(node) {
|
|
29558
30170
|
const content = node.content?.value || "";
|
|
@@ -29617,7 +30229,9 @@ class ERBNoCaseNodeChildrenVisitor extends BaseRuleVisitor {
|
|
|
29617
30229
|
for (const child of node.children) {
|
|
29618
30230
|
if (!this.isAllowedContent(child)) {
|
|
29619
30231
|
const childCode = IdentityPrinter.print(child).trim();
|
|
29620
|
-
this.
|
|
30232
|
+
const offense = this.createOffense(`Do not place \`${childCode}\` between \`${caseCode}\` and \`${conditionCode}\`. Content here is not part of any branch and will not be rendered.`, child.location);
|
|
30233
|
+
offense.tags = ["unnecessary"];
|
|
30234
|
+
this.offenses.push(offense);
|
|
29621
30235
|
}
|
|
29622
30236
|
}
|
|
29623
30237
|
}
|
|
@@ -29647,6 +30261,193 @@ class ERBNoCaseNodeChildrenRule extends ParserRule {
|
|
|
29647
30261
|
}
|
|
29648
30262
|
}
|
|
29649
30263
|
|
|
30264
|
+
class ERBNoEmptyControlFlowVisitor extends BaseRuleVisitor {
|
|
30265
|
+
processedIfNodes = new Set();
|
|
30266
|
+
processedElseNodes = new Set();
|
|
30267
|
+
visitERBIfNode(node) {
|
|
30268
|
+
if (this.processedIfNodes.has(node)) {
|
|
30269
|
+
return;
|
|
30270
|
+
}
|
|
30271
|
+
this.markIfChainAsProcessed(node);
|
|
30272
|
+
this.markElseNodesInIfChain(node);
|
|
30273
|
+
const entireChainEmpty = this.isEntireIfChainEmpty(node);
|
|
30274
|
+
if (entireChainEmpty) {
|
|
30275
|
+
this.addEmptyBlockOffense(node, node.statements, "if");
|
|
30276
|
+
}
|
|
30277
|
+
else {
|
|
30278
|
+
this.checkIfChainParts(node);
|
|
30279
|
+
}
|
|
30280
|
+
this.visitChildNodes(node);
|
|
30281
|
+
}
|
|
30282
|
+
visitERBElseNode(node) {
|
|
30283
|
+
if (this.processedElseNodes.has(node)) {
|
|
30284
|
+
this.visitChildNodes(node);
|
|
30285
|
+
return;
|
|
30286
|
+
}
|
|
30287
|
+
this.addEmptyBlockOffense(node, node.statements, "else");
|
|
30288
|
+
this.visitChildNodes(node);
|
|
30289
|
+
}
|
|
30290
|
+
visitERBUnlessNode(node) {
|
|
30291
|
+
const unlessHasContent = this.statementsHaveContent(node.statements);
|
|
30292
|
+
const elseHasContent = node.else_clause && this.statementsHaveContent(node.else_clause.statements);
|
|
30293
|
+
if (node.else_clause) {
|
|
30294
|
+
this.processedElseNodes.add(node.else_clause);
|
|
30295
|
+
}
|
|
30296
|
+
const entireBlockEmpty = !unlessHasContent && !elseHasContent;
|
|
30297
|
+
if (entireBlockEmpty) {
|
|
30298
|
+
this.addEmptyBlockOffense(node, node.statements, "unless");
|
|
30299
|
+
}
|
|
30300
|
+
else {
|
|
30301
|
+
if (!unlessHasContent) {
|
|
30302
|
+
this.addEmptyBlockOffenseWithEnd(node, node.statements, "unless", node.else_clause);
|
|
30303
|
+
}
|
|
30304
|
+
if (node.else_clause && !elseHasContent) {
|
|
30305
|
+
this.addEmptyBlockOffense(node.else_clause, node.else_clause.statements, "else");
|
|
30306
|
+
}
|
|
30307
|
+
}
|
|
30308
|
+
this.visitChildNodes(node);
|
|
30309
|
+
}
|
|
30310
|
+
visitERBForNode(node) {
|
|
30311
|
+
this.addEmptyBlockOffense(node, node.statements, "for");
|
|
30312
|
+
this.visitChildNodes(node);
|
|
30313
|
+
}
|
|
30314
|
+
visitERBWhileNode(node) {
|
|
30315
|
+
this.addEmptyBlockOffense(node, node.statements, "while");
|
|
30316
|
+
this.visitChildNodes(node);
|
|
30317
|
+
}
|
|
30318
|
+
visitERBUntilNode(node) {
|
|
30319
|
+
this.addEmptyBlockOffense(node, node.statements, "until");
|
|
30320
|
+
this.visitChildNodes(node);
|
|
30321
|
+
}
|
|
30322
|
+
visitERBWhenNode(node) {
|
|
30323
|
+
if (!node.then_keyword) {
|
|
30324
|
+
this.addEmptyBlockOffense(node, node.statements, "when");
|
|
30325
|
+
}
|
|
30326
|
+
this.visitChildNodes(node);
|
|
30327
|
+
}
|
|
30328
|
+
visitERBInNode(node) {
|
|
30329
|
+
if (!node.then_keyword) {
|
|
30330
|
+
this.addEmptyBlockOffense(node, node.statements, "in");
|
|
30331
|
+
}
|
|
30332
|
+
this.visitChildNodes(node);
|
|
30333
|
+
}
|
|
30334
|
+
visitERBBeginNode(node) {
|
|
30335
|
+
this.addEmptyBlockOffense(node, node.statements, "begin");
|
|
30336
|
+
this.visitChildNodes(node);
|
|
30337
|
+
}
|
|
30338
|
+
visitERBRescueNode(node) {
|
|
30339
|
+
this.addEmptyBlockOffense(node, node.statements, "rescue");
|
|
30340
|
+
this.visitChildNodes(node);
|
|
30341
|
+
}
|
|
30342
|
+
visitERBEnsureNode(node) {
|
|
30343
|
+
this.addEmptyBlockOffense(node, node.statements, "ensure");
|
|
30344
|
+
this.visitChildNodes(node);
|
|
30345
|
+
}
|
|
30346
|
+
visitERBBlockNode(node) {
|
|
30347
|
+
this.addEmptyBlockOffense(node, node.body, "do");
|
|
30348
|
+
this.visitChildNodes(node);
|
|
30349
|
+
}
|
|
30350
|
+
addEmptyBlockOffense(node, statements, blockType) {
|
|
30351
|
+
this.addEmptyBlockOffenseWithEnd(node, statements, blockType, null);
|
|
30352
|
+
}
|
|
30353
|
+
addEmptyBlockOffenseWithEnd(node, statements, blockType, subsequentNode) {
|
|
30354
|
+
if (this.statementsHaveContent(statements)) {
|
|
30355
|
+
return;
|
|
30356
|
+
}
|
|
30357
|
+
const startLocation = node.location.start;
|
|
30358
|
+
const endLocation = subsequentNode
|
|
30359
|
+
? subsequentNode.location.start
|
|
30360
|
+
: node.location.end;
|
|
30361
|
+
const location = Location.from(startLocation.line, startLocation.column, endLocation.line, endLocation.column);
|
|
30362
|
+
const offense = this.createOffense(`Empty ${blockType} block: this control flow statement has no content`, location);
|
|
30363
|
+
offense.tags = ["unnecessary"];
|
|
30364
|
+
this.offenses.push(offense);
|
|
30365
|
+
}
|
|
30366
|
+
statementsHaveContent(statements) {
|
|
30367
|
+
return statements.some(statement => {
|
|
30368
|
+
if (isHTMLTextNode(statement)) {
|
|
30369
|
+
return statement.content.trim() !== "";
|
|
30370
|
+
}
|
|
30371
|
+
return true;
|
|
30372
|
+
});
|
|
30373
|
+
}
|
|
30374
|
+
markIfChainAsProcessed(node) {
|
|
30375
|
+
this.processedIfNodes.add(node);
|
|
30376
|
+
this.traverseSubsequentNodes(node.subsequent, (current) => {
|
|
30377
|
+
if (isERBIfNode(current)) {
|
|
30378
|
+
this.processedIfNodes.add(current);
|
|
30379
|
+
}
|
|
30380
|
+
});
|
|
30381
|
+
}
|
|
30382
|
+
markElseNodesInIfChain(node) {
|
|
30383
|
+
this.traverseSubsequentNodes(node.subsequent, (current) => {
|
|
30384
|
+
if (isERBElseNode(current)) {
|
|
30385
|
+
this.processedElseNodes.add(current);
|
|
30386
|
+
}
|
|
30387
|
+
});
|
|
30388
|
+
}
|
|
30389
|
+
traverseSubsequentNodes(startNode, callback) {
|
|
30390
|
+
let current = startNode;
|
|
30391
|
+
while (current) {
|
|
30392
|
+
if (isERBIfNode(current)) {
|
|
30393
|
+
callback(current);
|
|
30394
|
+
current = current.subsequent;
|
|
30395
|
+
}
|
|
30396
|
+
else if (isERBElseNode(current)) {
|
|
30397
|
+
callback(current);
|
|
30398
|
+
break;
|
|
30399
|
+
}
|
|
30400
|
+
else {
|
|
30401
|
+
break;
|
|
30402
|
+
}
|
|
30403
|
+
}
|
|
30404
|
+
}
|
|
30405
|
+
checkIfChainParts(node) {
|
|
30406
|
+
if (!this.statementsHaveContent(node.statements)) {
|
|
30407
|
+
this.addEmptyBlockOffenseWithEnd(node, node.statements, "if", node.subsequent);
|
|
30408
|
+
}
|
|
30409
|
+
this.traverseSubsequentNodes(node.subsequent, (current) => {
|
|
30410
|
+
if (this.statementsHaveContent(current.statements)) {
|
|
30411
|
+
return;
|
|
30412
|
+
}
|
|
30413
|
+
const blockType = isERBIfNode(current) ? "elsif" : "else";
|
|
30414
|
+
const nextSubsequent = isERBIfNode(current) ? current.subsequent : null;
|
|
30415
|
+
if (nextSubsequent) {
|
|
30416
|
+
this.addEmptyBlockOffenseWithEnd(current, current.statements, blockType, nextSubsequent);
|
|
30417
|
+
}
|
|
30418
|
+
else {
|
|
30419
|
+
this.addEmptyBlockOffense(current, current.statements, blockType);
|
|
30420
|
+
}
|
|
30421
|
+
});
|
|
30422
|
+
}
|
|
30423
|
+
isEntireIfChainEmpty(node) {
|
|
30424
|
+
if (this.statementsHaveContent(node.statements)) {
|
|
30425
|
+
return false;
|
|
30426
|
+
}
|
|
30427
|
+
let hasContent = false;
|
|
30428
|
+
this.traverseSubsequentNodes(node.subsequent, (current) => {
|
|
30429
|
+
if (this.statementsHaveContent(current.statements)) {
|
|
30430
|
+
hasContent = true;
|
|
30431
|
+
}
|
|
30432
|
+
});
|
|
30433
|
+
return !hasContent;
|
|
30434
|
+
}
|
|
30435
|
+
}
|
|
30436
|
+
class ERBNoEmptyControlFlowRule extends ParserRule {
|
|
30437
|
+
static ruleName = "erb-no-empty-control-flow";
|
|
30438
|
+
get defaultConfig() {
|
|
30439
|
+
return {
|
|
30440
|
+
enabled: true,
|
|
30441
|
+
severity: "hint"
|
|
30442
|
+
};
|
|
30443
|
+
}
|
|
30444
|
+
check(result, context) {
|
|
30445
|
+
const visitor = new ERBNoEmptyControlFlowVisitor(this.ruleName, context);
|
|
30446
|
+
visitor.visit(result.value);
|
|
30447
|
+
return visitor.offenses;
|
|
30448
|
+
}
|
|
30449
|
+
}
|
|
30450
|
+
|
|
29650
30451
|
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
29651
30452
|
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
29652
30453
|
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
@@ -29805,6 +30606,15 @@ class ERBNoConditionalOpenTagRule extends ParserRule {
|
|
|
29805
30606
|
function getSignificantNodes(statements) {
|
|
29806
30607
|
return statements.filter(node => !isPureWhitespaceNode(node));
|
|
29807
30608
|
}
|
|
30609
|
+
function trimWhitespaceNodes(nodes) {
|
|
30610
|
+
let start = 0;
|
|
30611
|
+
let end = nodes.length;
|
|
30612
|
+
while (start < end && isPureWhitespaceNode(nodes[start]))
|
|
30613
|
+
start++;
|
|
30614
|
+
while (end > start && isPureWhitespaceNode(nodes[end - 1]))
|
|
30615
|
+
end--;
|
|
30616
|
+
return nodes.slice(start, end);
|
|
30617
|
+
}
|
|
29808
30618
|
function allEquivalentElements(nodes) {
|
|
29809
30619
|
if (nodes.length < 2)
|
|
29810
30620
|
return false;
|
|
@@ -29922,9 +30732,19 @@ class ERBNoDuplicateBranchElementsVisitor extends BaseRuleVisitor {
|
|
|
29922
30732
|
if (isERBIfNode(node)) {
|
|
29923
30733
|
this.markSubsequentIfNodesAsProcessed(node);
|
|
29924
30734
|
}
|
|
30735
|
+
if (this.allBranchesIdentical(branches)) {
|
|
30736
|
+
this.addOffense("All branches of this conditional have identical content. The conditional can be removed.", node.location, { node: node, allIdentical: true }, "warning");
|
|
30737
|
+
return;
|
|
30738
|
+
}
|
|
29925
30739
|
const state = { isFirstOffense: true };
|
|
29926
30740
|
this.checkBranches(branches, node, state);
|
|
29927
30741
|
}
|
|
30742
|
+
allBranchesIdentical(branches) {
|
|
30743
|
+
if (branches.length < 2)
|
|
30744
|
+
return false;
|
|
30745
|
+
const first = branches[0].map(node => IdentityPrinter.print(node)).join("");
|
|
30746
|
+
return branches.slice(1).every(branch => branch.map(node => IdentityPrinter.print(node)).join("") === first);
|
|
30747
|
+
}
|
|
29928
30748
|
markSubsequentIfNodesAsProcessed(node) {
|
|
29929
30749
|
let current = node.subsequent;
|
|
29930
30750
|
while (current) {
|
|
@@ -29958,11 +30778,23 @@ class ERBNoDuplicateBranchElementsVisitor extends BaseRuleVisitor {
|
|
|
29958
30778
|
const bodiesMatch = elements.every(element => IdentityPrinter.print(element) === IdentityPrinter.print(elements[0]));
|
|
29959
30779
|
for (const element of elements) {
|
|
29960
30780
|
const printed = IdentityPrinter.print(element.open_tag);
|
|
29961
|
-
|
|
29962
|
-
|
|
29963
|
-
|
|
29964
|
-
|
|
29965
|
-
|
|
30781
|
+
if (bodiesMatch) {
|
|
30782
|
+
const autofixContext = state.isFirstOffense
|
|
30783
|
+
? { node: conditionalNode }
|
|
30784
|
+
: undefined;
|
|
30785
|
+
this.addOffense(`The \`${printed}\` element is duplicated across all branches of this conditional and can be moved outside.`, element.location, autofixContext);
|
|
30786
|
+
state.isFirstOffense = false;
|
|
30787
|
+
}
|
|
30788
|
+
else {
|
|
30789
|
+
const autofixContext = state.isFirstOffense
|
|
30790
|
+
? { node: conditionalNode }
|
|
30791
|
+
: undefined;
|
|
30792
|
+
const tagNameLocation = isHTMLOpenTagNode(element.open_tag) && element.open_tag.tag_name?.location
|
|
30793
|
+
? element.open_tag.tag_name.location
|
|
30794
|
+
: element?.open_tag?.location || element.location;
|
|
30795
|
+
this.addOffense(`The \`${printed}\` tag is repeated across all branches with different content. Consider extracting the shared tag outside the conditional.`, tagNameLocation, autofixContext, "hint");
|
|
30796
|
+
state.isFirstOffense = false;
|
|
30797
|
+
}
|
|
29966
30798
|
}
|
|
29967
30799
|
if (!bodiesMatch && bodies.every(body => body.length > 0)) {
|
|
29968
30800
|
this.checkBranches(bodies, conditionalNode, state);
|
|
@@ -29991,6 +30823,15 @@ class ERBNoDuplicateBranchElementsRule extends ParserRule {
|
|
|
29991
30823
|
const branches = collectBranches(conditionalNode);
|
|
29992
30824
|
if (!branches)
|
|
29993
30825
|
return null;
|
|
30826
|
+
if (offense.autofixContext.allIdentical) {
|
|
30827
|
+
const parentInfo = findParentArray(result.value, conditionalNode);
|
|
30828
|
+
if (!parentInfo)
|
|
30829
|
+
return null;
|
|
30830
|
+
const { array: parentArray, index: conditionalIndex } = parentInfo;
|
|
30831
|
+
const firstBranchContent = trimWhitespaceNodes(branches[0]);
|
|
30832
|
+
parentArray.splice(conditionalIndex, 1, ...firstBranchContent);
|
|
30833
|
+
return result;
|
|
30834
|
+
}
|
|
29994
30835
|
const significantBranches = branches.map(getSignificantNodes);
|
|
29995
30836
|
if (significantBranches.some(branch => branch.length === 0))
|
|
29996
30837
|
return null;
|
|
@@ -30004,23 +30845,51 @@ class ERBNoDuplicateBranchElementsRule extends ParserRule {
|
|
|
30004
30845
|
return null;
|
|
30005
30846
|
let { array: parentArray, index: conditionalIndex } = parentInfo;
|
|
30006
30847
|
let hasWrapped = false;
|
|
30848
|
+
let didMutate = false;
|
|
30849
|
+
let failedToHoistPrefix = false;
|
|
30850
|
+
let hoistedBefore = false;
|
|
30007
30851
|
const hoistElement = (elements, position) => {
|
|
30852
|
+
const actualPosition = (position === "before" && failedToHoistPrefix) ? "after" : position;
|
|
30008
30853
|
const bodiesMatch = elements.every(element => IdentityPrinter.print(element) === IdentityPrinter.print(elements[0]));
|
|
30009
30854
|
if (bodiesMatch) {
|
|
30855
|
+
if (actualPosition === "after") {
|
|
30856
|
+
const currentLengths = branches.map(b => getSignificantNodes(b).length);
|
|
30857
|
+
if (currentLengths.some(l => l !== currentLengths[0]))
|
|
30858
|
+
return;
|
|
30859
|
+
}
|
|
30860
|
+
if (actualPosition === "after" && position === "before") {
|
|
30861
|
+
const isAtEnd = branches.every((branch, index) => {
|
|
30862
|
+
const nodes = getSignificantNodes(branch);
|
|
30863
|
+
return nodes.length > 0 && nodes[nodes.length - 1] === elements[index];
|
|
30864
|
+
});
|
|
30865
|
+
if (!isAtEnd)
|
|
30866
|
+
return;
|
|
30867
|
+
}
|
|
30010
30868
|
for (let i = 0; i < branches.length; i++) {
|
|
30011
30869
|
removeNodeFromArray(branches[i], elements[i]);
|
|
30012
30870
|
}
|
|
30013
|
-
if (
|
|
30014
|
-
parentArray.splice(conditionalIndex, 0, elements[0]);
|
|
30015
|
-
conditionalIndex
|
|
30871
|
+
if (actualPosition === "before") {
|
|
30872
|
+
parentArray.splice(conditionalIndex, 0, elements[0], createLiteral("\n"));
|
|
30873
|
+
conditionalIndex += 2;
|
|
30874
|
+
hoistedBefore = true;
|
|
30016
30875
|
}
|
|
30017
30876
|
else {
|
|
30018
|
-
parentArray.splice(conditionalIndex + 1, 0, elements[0]);
|
|
30877
|
+
parentArray.splice(conditionalIndex + 1, 0, createLiteral("\n"), elements[0]);
|
|
30019
30878
|
}
|
|
30879
|
+
didMutate = true;
|
|
30020
30880
|
}
|
|
30021
30881
|
else {
|
|
30022
30882
|
if (hasWrapped)
|
|
30023
30883
|
return;
|
|
30884
|
+
const canWrap = branches.every((branch, index) => {
|
|
30885
|
+
const remaining = getSignificantNodes(branch);
|
|
30886
|
+
return remaining.length === 1 && remaining[0] === elements[index];
|
|
30887
|
+
});
|
|
30888
|
+
if (!canWrap) {
|
|
30889
|
+
if (position === "before")
|
|
30890
|
+
failedToHoistPrefix = true;
|
|
30891
|
+
return;
|
|
30892
|
+
}
|
|
30024
30893
|
for (let i = 0; i < branches.length; i++) {
|
|
30025
30894
|
replaceNodeWithBody(branches[i], elements[i]);
|
|
30026
30895
|
}
|
|
@@ -30029,6 +30898,7 @@ class ERBNoDuplicateBranchElementsRule extends ParserRule {
|
|
|
30029
30898
|
parentArray = wrapper.body;
|
|
30030
30899
|
conditionalIndex = 1;
|
|
30031
30900
|
hasWrapped = true;
|
|
30901
|
+
didMutate = true;
|
|
30032
30902
|
}
|
|
30033
30903
|
};
|
|
30034
30904
|
for (let index = 0; index < prefixCount; index++) {
|
|
@@ -30039,7 +30909,22 @@ class ERBNoDuplicateBranchElementsRule extends ParserRule {
|
|
|
30039
30909
|
const elements = significantBranches.map(branch => branch[branch.length - 1 - offset]);
|
|
30040
30910
|
hoistElement(elements, "after");
|
|
30041
30911
|
}
|
|
30042
|
-
|
|
30912
|
+
if (!hasWrapped && hoistedBefore) {
|
|
30913
|
+
const remaining = branches.map(branch => getSignificantNodes(branch));
|
|
30914
|
+
if (remaining.every(branch => branch.length === 1) && allEquivalentElements(remaining.map(b => b[0]))) {
|
|
30915
|
+
const elements = remaining.map(b => b[0]);
|
|
30916
|
+
const bodiesMatch = elements.every(el => IdentityPrinter.print(el) === IdentityPrinter.print(elements[0]));
|
|
30917
|
+
if (!bodiesMatch && elements.every(el => el.body.length > 0)) {
|
|
30918
|
+
for (let i = 0; i < branches.length; i++) {
|
|
30919
|
+
replaceNodeWithBody(branches[i], elements[i]);
|
|
30920
|
+
}
|
|
30921
|
+
const wrapper = createWrapper(elements[0], [createLiteral("\n"), conditionalNode, createLiteral("\n")]);
|
|
30922
|
+
parentArray[conditionalIndex] = wrapper;
|
|
30923
|
+
didMutate = true;
|
|
30924
|
+
}
|
|
30925
|
+
}
|
|
30926
|
+
}
|
|
30927
|
+
return didMutate ? result : null;
|
|
30043
30928
|
}
|
|
30044
30929
|
}
|
|
30045
30930
|
|
|
@@ -30565,6 +31450,47 @@ class ERBNoRawOutputInAttributeValueRule extends ParserRule {
|
|
|
30565
31450
|
}
|
|
30566
31451
|
}
|
|
30567
31452
|
|
|
31453
|
+
function isAssignmentNode(prismNode) {
|
|
31454
|
+
const type = prismNode?.constructor?.name;
|
|
31455
|
+
if (!type)
|
|
31456
|
+
return false;
|
|
31457
|
+
return type.endsWith("WriteNode");
|
|
31458
|
+
}
|
|
31459
|
+
class ERBNoSilentStatementVisitor extends BaseRuleVisitor {
|
|
31460
|
+
visitERBContentNode(node) {
|
|
31461
|
+
if (isERBOutputNode(node))
|
|
31462
|
+
return;
|
|
31463
|
+
const prismNode = node.prismNode;
|
|
31464
|
+
if (!prismNode)
|
|
31465
|
+
return;
|
|
31466
|
+
if (isAssignmentNode(prismNode))
|
|
31467
|
+
return;
|
|
31468
|
+
const content = node.content?.value?.trim();
|
|
31469
|
+
if (!content)
|
|
31470
|
+
return;
|
|
31471
|
+
this.addOffense(`Avoid using silent ERB tags for statements. Move \`${content}\` to a controller, helper, or presenter.`, node.location);
|
|
31472
|
+
}
|
|
31473
|
+
}
|
|
31474
|
+
class ERBNoSilentStatementRule extends ParserRule {
|
|
31475
|
+
static ruleName = "erb-no-silent-statement";
|
|
31476
|
+
get defaultConfig() {
|
|
31477
|
+
return {
|
|
31478
|
+
enabled: false,
|
|
31479
|
+
severity: "warning"
|
|
31480
|
+
};
|
|
31481
|
+
}
|
|
31482
|
+
get parserOptions() {
|
|
31483
|
+
return {
|
|
31484
|
+
prism_nodes: true,
|
|
31485
|
+
};
|
|
31486
|
+
}
|
|
31487
|
+
check(result, context) {
|
|
31488
|
+
const visitor = new ERBNoSilentStatementVisitor(this.ruleName, context);
|
|
31489
|
+
visitor.visit(result.value);
|
|
31490
|
+
return visitor.offenses;
|
|
31491
|
+
}
|
|
31492
|
+
}
|
|
31493
|
+
|
|
30568
31494
|
class ERBNoSilentTagInAttributeNameVisitor extends BaseRuleVisitor {
|
|
30569
31495
|
visitHTMLAttributeNameNode(node) {
|
|
30570
31496
|
const erbNodes = filterERBContentNodes(node.children);
|
|
@@ -30827,7 +31753,7 @@ class ERBNoTrailingWhitespaceRule extends ParserRule {
|
|
|
30827
31753
|
}
|
|
30828
31754
|
|
|
30829
31755
|
const JS_ATTRIBUTE_PATTERN = /^on/i;
|
|
30830
|
-
const SAFE_PATTERN
|
|
31756
|
+
const SAFE_PATTERN = /\.to_json\s*$|\bj\s*[\s(]|\bescape_javascript\s*[\s(]/;
|
|
30831
31757
|
class ERBNoUnsafeJSAttributeVisitor extends AttributeVisitorMixin {
|
|
30832
31758
|
checkStaticAttributeDynamicValue({ attributeName, valueNodes }) {
|
|
30833
31759
|
if (!JS_ATTRIBUTE_PATTERN.test(attributeName))
|
|
@@ -30838,7 +31764,7 @@ class ERBNoUnsafeJSAttributeVisitor extends AttributeVisitorMixin {
|
|
|
30838
31764
|
if (!isERBOutputNode(node))
|
|
30839
31765
|
continue;
|
|
30840
31766
|
const content = node.content?.value?.trim() || "";
|
|
30841
|
-
if (SAFE_PATTERN
|
|
31767
|
+
if (SAFE_PATTERN.test(content))
|
|
30842
31768
|
continue;
|
|
30843
31769
|
this.addOffense(`Unsafe ERB output in \`${attributeName}\` attribute. Use \`.to_json\`, \`j()\`, or \`escape_javascript()\` to safely encode values.`, node.location);
|
|
30844
31770
|
}
|
|
@@ -30919,7 +31845,27 @@ class ERBNoUnsafeRawRule extends ParserRule {
|
|
|
30919
31845
|
}
|
|
30920
31846
|
}
|
|
30921
31847
|
|
|
30922
|
-
const
|
|
31848
|
+
const SAFE_METHOD_NAMES = new Set([
|
|
31849
|
+
"to_json",
|
|
31850
|
+
"json_escape",
|
|
31851
|
+
]);
|
|
31852
|
+
const ESCAPE_JAVASCRIPT_METHOD_NAMES = new Set([
|
|
31853
|
+
"j",
|
|
31854
|
+
"escape_javascript",
|
|
31855
|
+
]);
|
|
31856
|
+
class SafeCallDetector extends Visitor$1 {
|
|
31857
|
+
hasSafeCall = false;
|
|
31858
|
+
hasEscapeJavascriptCall = false;
|
|
31859
|
+
visitCallNode(node) {
|
|
31860
|
+
if (SAFE_METHOD_NAMES.has(node.name)) {
|
|
31861
|
+
this.hasSafeCall = true;
|
|
31862
|
+
}
|
|
31863
|
+
if (ESCAPE_JAVASCRIPT_METHOD_NAMES.has(node.name)) {
|
|
31864
|
+
this.hasEscapeJavascriptCall = true;
|
|
31865
|
+
}
|
|
31866
|
+
this.visitChildNodes(node);
|
|
31867
|
+
}
|
|
31868
|
+
}
|
|
30923
31869
|
class ERBNoUnsafeScriptInterpolationVisitor extends BaseRuleVisitor {
|
|
30924
31870
|
visitHTMLElementNode(node) {
|
|
30925
31871
|
if (!isHTMLOpenTagNode(node.open_tag)) {
|
|
@@ -30948,9 +31894,17 @@ class ERBNoUnsafeScriptInterpolationVisitor extends BaseRuleVisitor {
|
|
|
30948
31894
|
continue;
|
|
30949
31895
|
if (!isERBOutputNode(child))
|
|
30950
31896
|
continue;
|
|
30951
|
-
const
|
|
30952
|
-
|
|
31897
|
+
const erbContent = child;
|
|
31898
|
+
const prismNode = erbContent.prismNode;
|
|
31899
|
+
const detector = new SafeCallDetector();
|
|
31900
|
+
if (prismNode)
|
|
31901
|
+
detector.visit(prismNode);
|
|
31902
|
+
if (detector.hasSafeCall)
|
|
31903
|
+
continue;
|
|
31904
|
+
if (detector.hasEscapeJavascriptCall) {
|
|
31905
|
+
this.addOffense("Avoid `j()` / `escape_javascript()` in `<script>` tags. It is only safe inside quoted string literals. Use `.to_json` instead, which is safe in any position.", child.location);
|
|
30953
31906
|
continue;
|
|
31907
|
+
}
|
|
30954
31908
|
this.addOffense("Unsafe ERB output in `<script>` tag. Use `.to_json` to safely serialize values into JavaScript.", child.location);
|
|
30955
31909
|
}
|
|
30956
31910
|
}
|
|
@@ -30963,6 +31917,11 @@ class ERBNoUnsafeScriptInterpolationRule extends ParserRule {
|
|
|
30963
31917
|
severity: "error"
|
|
30964
31918
|
};
|
|
30965
31919
|
}
|
|
31920
|
+
get parserOptions() {
|
|
31921
|
+
return {
|
|
31922
|
+
prism_nodes: true,
|
|
31923
|
+
};
|
|
31924
|
+
}
|
|
30966
31925
|
check(result, context) {
|
|
30967
31926
|
const visitor = new ERBNoUnsafeScriptInterpolationVisitor(this.ruleName, context);
|
|
30968
31927
|
visitor.visit(result.value);
|
|
@@ -31970,7 +32929,7 @@ class HerbDisableCommentValidRuleNameRule extends ParserRule {
|
|
|
31970
32929
|
}
|
|
31971
32930
|
}
|
|
31972
32931
|
|
|
31973
|
-
const ALLOWED_TYPES = ["text/javascript"];
|
|
32932
|
+
const ALLOWED_TYPES = ["text/javascript", "module", "importmap", "speculationrules"];
|
|
31974
32933
|
class AllowedScriptTypeVisitor extends BaseRuleVisitor {
|
|
31975
32934
|
visitHTMLOpenTagNode(node) {
|
|
31976
32935
|
if (getTagLocalName(node) === "script") {
|
|
@@ -32508,6 +33467,47 @@ class HTMLBodyOnlyElementsRule extends ParserRule {
|
|
|
32508
33467
|
}
|
|
32509
33468
|
}
|
|
32510
33469
|
|
|
33470
|
+
class BooleanAttributesNoValueVisitor extends AttributeVisitorMixin {
|
|
33471
|
+
checkStaticAttributeStaticValue({ originalAttributeName, attributeNode }) {
|
|
33472
|
+
this.checkAttribute(originalAttributeName, attributeNode);
|
|
33473
|
+
}
|
|
33474
|
+
checkStaticAttributeDynamicValue({ originalAttributeName, attributeNode }) {
|
|
33475
|
+
this.checkAttribute(originalAttributeName, attributeNode);
|
|
33476
|
+
}
|
|
33477
|
+
checkAttribute(attributeName, attributeNode) {
|
|
33478
|
+
if (!isBooleanAttribute(attributeName))
|
|
33479
|
+
return;
|
|
33480
|
+
if (!hasAttributeValue(attributeNode))
|
|
33481
|
+
return;
|
|
33482
|
+
this.addOffense(`Boolean attribute \`${IdentityPrinter.print(attributeNode.name)}\` should not have a value. Use \`${attributeName.toLowerCase()}\` instead of \`${IdentityPrinter.print(attributeNode)}\`.`, attributeNode.value.location, {
|
|
33483
|
+
node: attributeNode
|
|
33484
|
+
});
|
|
33485
|
+
}
|
|
33486
|
+
}
|
|
33487
|
+
class HTMLBooleanAttributesNoValueRule extends ParserRule {
|
|
33488
|
+
static autocorrectable = true;
|
|
33489
|
+
static ruleName = "html-boolean-attributes-no-value";
|
|
33490
|
+
get defaultConfig() {
|
|
33491
|
+
return {
|
|
33492
|
+
enabled: true,
|
|
33493
|
+
severity: "error"
|
|
33494
|
+
};
|
|
33495
|
+
}
|
|
33496
|
+
check(result, context) {
|
|
33497
|
+
const visitor = new BooleanAttributesNoValueVisitor(this.ruleName, context);
|
|
33498
|
+
visitor.visit(result.value);
|
|
33499
|
+
return visitor.offenses;
|
|
33500
|
+
}
|
|
33501
|
+
autofix(offense, result, _context) {
|
|
33502
|
+
if (!offense.autofixContext)
|
|
33503
|
+
return null;
|
|
33504
|
+
const { node } = offense.autofixContext;
|
|
33505
|
+
node.equals = null;
|
|
33506
|
+
node.value = null;
|
|
33507
|
+
return result;
|
|
33508
|
+
}
|
|
33509
|
+
}
|
|
33510
|
+
|
|
32511
33511
|
class DetailsHasSummaryVisitor extends BaseRuleVisitor {
|
|
32512
33512
|
visitHTMLElementNode(node) {
|
|
32513
33513
|
this.checkDetailsElement(node);
|
|
@@ -32557,47 +33557,6 @@ class HTMLDetailsHasSummaryRule extends ParserRule {
|
|
|
32557
33557
|
}
|
|
32558
33558
|
}
|
|
32559
33559
|
|
|
32560
|
-
class BooleanAttributesNoValueVisitor extends AttributeVisitorMixin {
|
|
32561
|
-
checkStaticAttributeStaticValue({ originalAttributeName, attributeNode }) {
|
|
32562
|
-
this.checkAttribute(originalAttributeName, attributeNode);
|
|
32563
|
-
}
|
|
32564
|
-
checkStaticAttributeDynamicValue({ originalAttributeName, attributeNode }) {
|
|
32565
|
-
this.checkAttribute(originalAttributeName, attributeNode);
|
|
32566
|
-
}
|
|
32567
|
-
checkAttribute(attributeName, attributeNode) {
|
|
32568
|
-
if (!isBooleanAttribute(attributeName))
|
|
32569
|
-
return;
|
|
32570
|
-
if (!hasAttributeValue(attributeNode))
|
|
32571
|
-
return;
|
|
32572
|
-
this.addOffense(`Boolean attribute \`${IdentityPrinter.print(attributeNode.name)}\` should not have a value. Use \`${attributeName.toLowerCase()}\` instead of \`${IdentityPrinter.print(attributeNode)}\`.`, attributeNode.value.location, {
|
|
32573
|
-
node: attributeNode
|
|
32574
|
-
});
|
|
32575
|
-
}
|
|
32576
|
-
}
|
|
32577
|
-
class HTMLBooleanAttributesNoValueRule extends ParserRule {
|
|
32578
|
-
static autocorrectable = true;
|
|
32579
|
-
static ruleName = "html-boolean-attributes-no-value";
|
|
32580
|
-
get defaultConfig() {
|
|
32581
|
-
return {
|
|
32582
|
-
enabled: true,
|
|
32583
|
-
severity: "error"
|
|
32584
|
-
};
|
|
32585
|
-
}
|
|
32586
|
-
check(result, context) {
|
|
32587
|
-
const visitor = new BooleanAttributesNoValueVisitor(this.ruleName, context);
|
|
32588
|
-
visitor.visit(result.value);
|
|
32589
|
-
return visitor.offenses;
|
|
32590
|
-
}
|
|
32591
|
-
autofix(offense, result, _context) {
|
|
32592
|
-
if (!offense.autofixContext)
|
|
32593
|
-
return null;
|
|
32594
|
-
const { node } = offense.autofixContext;
|
|
32595
|
-
node.equals = null;
|
|
32596
|
-
node.value = null;
|
|
32597
|
-
return result;
|
|
32598
|
-
}
|
|
32599
|
-
}
|
|
32600
|
-
|
|
32601
33560
|
class HeadOnlyElementsVisitor extends BaseRuleVisitor {
|
|
32602
33561
|
elementStack = [];
|
|
32603
33562
|
visitHTMLElementNode(node) {
|
|
@@ -34280,8 +35239,10 @@ class TurboPermanentRequireIdRule extends ParserRule {
|
|
|
34280
35239
|
|
|
34281
35240
|
const rules = [
|
|
34282
35241
|
ActionViewNoSilentHelperRule,
|
|
35242
|
+
ActionViewNoSilentRenderRule,
|
|
34283
35243
|
ERBCommentSyntax,
|
|
34284
35244
|
ERBNoCaseNodeChildrenRule,
|
|
35245
|
+
ERBNoEmptyControlFlowRule,
|
|
34285
35246
|
ERBNoConditionalHTMLElementRule,
|
|
34286
35247
|
ERBNoConditionalOpenTagRule,
|
|
34287
35248
|
ERBNoDuplicateBranchElementsRule,
|
|
@@ -34296,6 +35257,7 @@ const rules = [
|
|
|
34296
35257
|
ERBNoOutputInAttributeNameRule,
|
|
34297
35258
|
ERBNoOutputInAttributePositionRule,
|
|
34298
35259
|
ERBNoRawOutputInAttributeValueRule,
|
|
35260
|
+
ERBNoSilentStatementRule,
|
|
34299
35261
|
ERBNoSilentTagInAttributeNameRule,
|
|
34300
35262
|
ERBNoStatementInScriptRule,
|
|
34301
35263
|
ERBNoThenInControlFlowRule,
|
|
@@ -34327,8 +35289,8 @@ const rules = [
|
|
|
34327
35289
|
HTMLAttributeValuesRequireQuotesRule,
|
|
34328
35290
|
HTMLAvoidBothDisabledAndAriaDisabledRule,
|
|
34329
35291
|
HTMLBodyOnlyElementsRule,
|
|
34330
|
-
HTMLDetailsHasSummaryRule,
|
|
34331
35292
|
HTMLBooleanAttributesNoValueRule,
|
|
35293
|
+
HTMLDetailsHasSummaryRule,
|
|
34332
35294
|
HTMLHeadOnlyElementsRule,
|
|
34333
35295
|
HTMLIframeHasTitleRule,
|
|
34334
35296
|
HTMLImgRequireAltRule,
|
|
@@ -35905,6 +36867,8 @@ async function loadCustomRules(options) {
|
|
|
35905
36867
|
|
|
35906
36868
|
exports.ABSTRACT_ARIA_ROLES = ABSTRACT_ARIA_ROLES;
|
|
35907
36869
|
exports.ARIA_ATTRIBUTES = ARIA_ATTRIBUTES;
|
|
36870
|
+
exports.ActionViewNoSilentHelperRule = ActionViewNoSilentHelperRule;
|
|
36871
|
+
exports.ActionViewNoSilentRenderRule = ActionViewNoSilentRenderRule;
|
|
35908
36872
|
exports.AttributeVisitorMixin = AttributeVisitorMixin;
|
|
35909
36873
|
exports.BaseLexerRuleVisitor = BaseLexerRuleVisitor;
|
|
35910
36874
|
exports.BaseRuleVisitor = BaseRuleVisitor;
|
|
@@ -35919,6 +36883,7 @@ exports.ERBCommentSyntax = ERBCommentSyntax;
|
|
|
35919
36883
|
exports.ERBNoCaseNodeChildrenRule = ERBNoCaseNodeChildrenRule;
|
|
35920
36884
|
exports.ERBNoConditionalOpenTagRule = ERBNoConditionalOpenTagRule;
|
|
35921
36885
|
exports.ERBNoDuplicateBranchElementsRule = ERBNoDuplicateBranchElementsRule;
|
|
36886
|
+
exports.ERBNoEmptyControlFlowRule = ERBNoEmptyControlFlowRule;
|
|
35922
36887
|
exports.ERBNoEmptyTagsRule = ERBNoEmptyTagsRule;
|
|
35923
36888
|
exports.ERBNoExtraNewLineRule = ERBNoExtraNewLineRule;
|
|
35924
36889
|
exports.ERBNoExtraWhitespaceRule = ERBNoExtraWhitespaceRule;
|
|
@@ -35929,6 +36894,7 @@ exports.ERBNoOutputControlFlowRule = ERBNoOutputControlFlowRule;
|
|
|
35929
36894
|
exports.ERBNoOutputInAttributeNameRule = ERBNoOutputInAttributeNameRule;
|
|
35930
36895
|
exports.ERBNoOutputInAttributePositionRule = ERBNoOutputInAttributePositionRule;
|
|
35931
36896
|
exports.ERBNoRawOutputInAttributeValueRule = ERBNoRawOutputInAttributeValueRule;
|
|
36897
|
+
exports.ERBNoSilentStatementRule = ERBNoSilentStatementRule;
|
|
35932
36898
|
exports.ERBNoSilentTagInAttributeNameRule = ERBNoSilentTagInAttributeNameRule;
|
|
35933
36899
|
exports.ERBNoStatementInScriptRule = ERBNoStatementInScriptRule;
|
|
35934
36900
|
exports.ERBNoThenInControlFlowRule = ERBNoThenInControlFlowRule;
|
|
@@ -36003,11 +36969,29 @@ exports.SVG_LOWERCASE_TO_CAMELCASE = SVG_LOWERCASE_TO_CAMELCASE;
|
|
|
36003
36969
|
exports.SourceRule = SourceRule;
|
|
36004
36970
|
exports.VALID_ARIA_ROLES = VALID_ARIA_ROLES;
|
|
36005
36971
|
exports.createEndOfFileLocation = createEndOfFileLocation;
|
|
36972
|
+
exports.findAttributeByName = findAttributeByName;
|
|
36006
36973
|
exports.findNodeAtPosition = findNodeAtPosition;
|
|
36007
36974
|
exports.findNodeByLocation = findNodeByLocation;
|
|
36008
36975
|
exports.findParent = findParent;
|
|
36976
|
+
exports.getAttribute = getAttribute;
|
|
36977
|
+
exports.getAttributeName = getAttributeName;
|
|
36978
|
+
exports.getAttributeValue = getAttributeValue;
|
|
36979
|
+
exports.getAttributeValueNodes = getAttributeValueNodes;
|
|
36980
|
+
exports.getAttributeValueQuoteType = getAttributeValueQuoteType;
|
|
36981
|
+
exports.getAttributes = getAttributes;
|
|
36009
36982
|
exports.getBasename = getBasename;
|
|
36983
|
+
exports.getCombinedAttributeNameString = getCombinedAttributeNameString;
|
|
36984
|
+
exports.getStaticAttributeValue = getStaticAttributeValue;
|
|
36985
|
+
exports.getStaticAttributeValueContent = getStaticAttributeValueContent;
|
|
36986
|
+
exports.getTagName = getTagName;
|
|
36987
|
+
exports.hasAttribute = hasAttribute;
|
|
36988
|
+
exports.hasAttributeValue = hasAttributeValue;
|
|
36010
36989
|
exports.hasBalancedParentheses = hasBalancedParentheses;
|
|
36990
|
+
exports.hasDynamicAttributeName = hasDynamicAttributeName;
|
|
36991
|
+
exports.hasDynamicAttributeValue = hasDynamicAttributeValue;
|
|
36992
|
+
exports.hasStaticAttributeValue = hasStaticAttributeValue;
|
|
36993
|
+
exports.hasStaticAttributeValueContent = hasStaticAttributeValueContent;
|
|
36994
|
+
exports.isAttributeValueQuoted = isAttributeValueQuoted;
|
|
36011
36995
|
exports.isBlockElement = isBlockElement;
|
|
36012
36996
|
exports.isBodyOnlyTag = isBodyOnlyTag;
|
|
36013
36997
|
exports.isBodyTag = isBodyTag;
|