@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.js
CHANGED
|
@@ -2306,7 +2306,7 @@ class Token {
|
|
|
2306
2306
|
}
|
|
2307
2307
|
|
|
2308
2308
|
// NOTE: This file is generated by the templates/template.rb script and should not
|
|
2309
|
-
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.
|
|
2309
|
+
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.2/templates/javascript/packages/core/src/errors.ts.erb
|
|
2310
2310
|
class HerbError {
|
|
2311
2311
|
type;
|
|
2312
2312
|
message;
|
|
@@ -3307,6 +3307,279 @@ class NestedERBTagError extends HerbError {
|
|
|
3307
3307
|
return output;
|
|
3308
3308
|
}
|
|
3309
3309
|
}
|
|
3310
|
+
class RenderAmbiguousLocalsError extends HerbError {
|
|
3311
|
+
partial;
|
|
3312
|
+
static from(data) {
|
|
3313
|
+
return new RenderAmbiguousLocalsError({
|
|
3314
|
+
type: data.type,
|
|
3315
|
+
message: data.message,
|
|
3316
|
+
location: Location.from(data.location),
|
|
3317
|
+
partial: data.partial,
|
|
3318
|
+
});
|
|
3319
|
+
}
|
|
3320
|
+
constructor(props) {
|
|
3321
|
+
super(props.type, props.message, props.location);
|
|
3322
|
+
this.partial = props.partial;
|
|
3323
|
+
}
|
|
3324
|
+
toJSON() {
|
|
3325
|
+
return {
|
|
3326
|
+
...super.toJSON(),
|
|
3327
|
+
type: "RENDER_AMBIGUOUS_LOCALS_ERROR",
|
|
3328
|
+
partial: this.partial,
|
|
3329
|
+
};
|
|
3330
|
+
}
|
|
3331
|
+
toMonacoDiagnostic() {
|
|
3332
|
+
return {
|
|
3333
|
+
line: this.location.start.line,
|
|
3334
|
+
column: this.location.start.column,
|
|
3335
|
+
endLine: this.location.end.line,
|
|
3336
|
+
endColumn: this.location.end.column,
|
|
3337
|
+
message: this.message,
|
|
3338
|
+
severity: 'error'
|
|
3339
|
+
};
|
|
3340
|
+
}
|
|
3341
|
+
treeInspect() {
|
|
3342
|
+
let output = "";
|
|
3343
|
+
output += `@ RenderAmbiguousLocalsError ${this.location.treeInspectWithLabel()}\n`;
|
|
3344
|
+
output += `├── message: "${this.message}"\n`;
|
|
3345
|
+
output += `└── partial: ${JSON.stringify(this.partial)}\n`;
|
|
3346
|
+
return output;
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
class RenderMissingLocalsError extends HerbError {
|
|
3350
|
+
partial;
|
|
3351
|
+
keywords;
|
|
3352
|
+
static from(data) {
|
|
3353
|
+
return new RenderMissingLocalsError({
|
|
3354
|
+
type: data.type,
|
|
3355
|
+
message: data.message,
|
|
3356
|
+
location: Location.from(data.location),
|
|
3357
|
+
partial: data.partial,
|
|
3358
|
+
keywords: data.keywords,
|
|
3359
|
+
});
|
|
3360
|
+
}
|
|
3361
|
+
constructor(props) {
|
|
3362
|
+
super(props.type, props.message, props.location);
|
|
3363
|
+
this.partial = props.partial;
|
|
3364
|
+
this.keywords = props.keywords;
|
|
3365
|
+
}
|
|
3366
|
+
toJSON() {
|
|
3367
|
+
return {
|
|
3368
|
+
...super.toJSON(),
|
|
3369
|
+
type: "RENDER_MISSING_LOCALS_ERROR",
|
|
3370
|
+
partial: this.partial,
|
|
3371
|
+
keywords: this.keywords,
|
|
3372
|
+
};
|
|
3373
|
+
}
|
|
3374
|
+
toMonacoDiagnostic() {
|
|
3375
|
+
return {
|
|
3376
|
+
line: this.location.start.line,
|
|
3377
|
+
column: this.location.start.column,
|
|
3378
|
+
endLine: this.location.end.line,
|
|
3379
|
+
endColumn: this.location.end.column,
|
|
3380
|
+
message: this.message,
|
|
3381
|
+
severity: 'error'
|
|
3382
|
+
};
|
|
3383
|
+
}
|
|
3384
|
+
treeInspect() {
|
|
3385
|
+
let output = "";
|
|
3386
|
+
output += `@ RenderMissingLocalsError ${this.location.treeInspectWithLabel()}\n`;
|
|
3387
|
+
output += `├── message: "${this.message}"\n`;
|
|
3388
|
+
output += `├── partial: ${JSON.stringify(this.partial)}\n`;
|
|
3389
|
+
output += `└── keywords: ${JSON.stringify(this.keywords)}\n`;
|
|
3390
|
+
return output;
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
class RenderNoArgumentsError extends HerbError {
|
|
3394
|
+
static from(data) {
|
|
3395
|
+
return new RenderNoArgumentsError({
|
|
3396
|
+
type: data.type,
|
|
3397
|
+
message: data.message,
|
|
3398
|
+
location: Location.from(data.location),
|
|
3399
|
+
});
|
|
3400
|
+
}
|
|
3401
|
+
constructor(props) {
|
|
3402
|
+
super(props.type, props.message, props.location);
|
|
3403
|
+
}
|
|
3404
|
+
toJSON() {
|
|
3405
|
+
return {
|
|
3406
|
+
...super.toJSON(),
|
|
3407
|
+
type: "RENDER_NO_ARGUMENTS_ERROR",
|
|
3408
|
+
};
|
|
3409
|
+
}
|
|
3410
|
+
toMonacoDiagnostic() {
|
|
3411
|
+
return {
|
|
3412
|
+
line: this.location.start.line,
|
|
3413
|
+
column: this.location.start.column,
|
|
3414
|
+
endLine: this.location.end.line,
|
|
3415
|
+
endColumn: this.location.end.column,
|
|
3416
|
+
message: this.message,
|
|
3417
|
+
severity: 'error'
|
|
3418
|
+
};
|
|
3419
|
+
}
|
|
3420
|
+
treeInspect() {
|
|
3421
|
+
let output = "";
|
|
3422
|
+
output += `@ RenderNoArgumentsError ${this.location.treeInspectWithLabel()}\n`;
|
|
3423
|
+
output += `└── message: "${this.message}"\n`;
|
|
3424
|
+
return output;
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
class RenderConflictingPartialError extends HerbError {
|
|
3428
|
+
positional_partial;
|
|
3429
|
+
keyword_partial;
|
|
3430
|
+
static from(data) {
|
|
3431
|
+
return new RenderConflictingPartialError({
|
|
3432
|
+
type: data.type,
|
|
3433
|
+
message: data.message,
|
|
3434
|
+
location: Location.from(data.location),
|
|
3435
|
+
positional_partial: data.positional_partial,
|
|
3436
|
+
keyword_partial: data.keyword_partial,
|
|
3437
|
+
});
|
|
3438
|
+
}
|
|
3439
|
+
constructor(props) {
|
|
3440
|
+
super(props.type, props.message, props.location);
|
|
3441
|
+
this.positional_partial = props.positional_partial;
|
|
3442
|
+
this.keyword_partial = props.keyword_partial;
|
|
3443
|
+
}
|
|
3444
|
+
toJSON() {
|
|
3445
|
+
return {
|
|
3446
|
+
...super.toJSON(),
|
|
3447
|
+
type: "RENDER_CONFLICTING_PARTIAL_ERROR",
|
|
3448
|
+
positional_partial: this.positional_partial,
|
|
3449
|
+
keyword_partial: this.keyword_partial,
|
|
3450
|
+
};
|
|
3451
|
+
}
|
|
3452
|
+
toMonacoDiagnostic() {
|
|
3453
|
+
return {
|
|
3454
|
+
line: this.location.start.line,
|
|
3455
|
+
column: this.location.start.column,
|
|
3456
|
+
endLine: this.location.end.line,
|
|
3457
|
+
endColumn: this.location.end.column,
|
|
3458
|
+
message: this.message,
|
|
3459
|
+
severity: 'error'
|
|
3460
|
+
};
|
|
3461
|
+
}
|
|
3462
|
+
treeInspect() {
|
|
3463
|
+
let output = "";
|
|
3464
|
+
output += `@ RenderConflictingPartialError ${this.location.treeInspectWithLabel()}\n`;
|
|
3465
|
+
output += `├── message: "${this.message}"\n`;
|
|
3466
|
+
output += `├── positional_partial: ${JSON.stringify(this.positional_partial)}\n`;
|
|
3467
|
+
output += `└── keyword_partial: ${JSON.stringify(this.keyword_partial)}\n`;
|
|
3468
|
+
return output;
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
class RenderInvalidAsOptionError extends HerbError {
|
|
3472
|
+
as_value;
|
|
3473
|
+
static from(data) {
|
|
3474
|
+
return new RenderInvalidAsOptionError({
|
|
3475
|
+
type: data.type,
|
|
3476
|
+
message: data.message,
|
|
3477
|
+
location: Location.from(data.location),
|
|
3478
|
+
as_value: data.as_value,
|
|
3479
|
+
});
|
|
3480
|
+
}
|
|
3481
|
+
constructor(props) {
|
|
3482
|
+
super(props.type, props.message, props.location);
|
|
3483
|
+
this.as_value = props.as_value;
|
|
3484
|
+
}
|
|
3485
|
+
toJSON() {
|
|
3486
|
+
return {
|
|
3487
|
+
...super.toJSON(),
|
|
3488
|
+
type: "RENDER_INVALID_AS_OPTION_ERROR",
|
|
3489
|
+
as_value: this.as_value,
|
|
3490
|
+
};
|
|
3491
|
+
}
|
|
3492
|
+
toMonacoDiagnostic() {
|
|
3493
|
+
return {
|
|
3494
|
+
line: this.location.start.line,
|
|
3495
|
+
column: this.location.start.column,
|
|
3496
|
+
endLine: this.location.end.line,
|
|
3497
|
+
endColumn: this.location.end.column,
|
|
3498
|
+
message: this.message,
|
|
3499
|
+
severity: 'error'
|
|
3500
|
+
};
|
|
3501
|
+
}
|
|
3502
|
+
treeInspect() {
|
|
3503
|
+
let output = "";
|
|
3504
|
+
output += `@ RenderInvalidAsOptionError ${this.location.treeInspectWithLabel()}\n`;
|
|
3505
|
+
output += `├── message: "${this.message}"\n`;
|
|
3506
|
+
output += `└── as_value: ${JSON.stringify(this.as_value)}\n`;
|
|
3507
|
+
return output;
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
class RenderObjectAndCollectionError extends HerbError {
|
|
3511
|
+
static from(data) {
|
|
3512
|
+
return new RenderObjectAndCollectionError({
|
|
3513
|
+
type: data.type,
|
|
3514
|
+
message: data.message,
|
|
3515
|
+
location: Location.from(data.location),
|
|
3516
|
+
});
|
|
3517
|
+
}
|
|
3518
|
+
constructor(props) {
|
|
3519
|
+
super(props.type, props.message, props.location);
|
|
3520
|
+
}
|
|
3521
|
+
toJSON() {
|
|
3522
|
+
return {
|
|
3523
|
+
...super.toJSON(),
|
|
3524
|
+
type: "RENDER_OBJECT_AND_COLLECTION_ERROR",
|
|
3525
|
+
};
|
|
3526
|
+
}
|
|
3527
|
+
toMonacoDiagnostic() {
|
|
3528
|
+
return {
|
|
3529
|
+
line: this.location.start.line,
|
|
3530
|
+
column: this.location.start.column,
|
|
3531
|
+
endLine: this.location.end.line,
|
|
3532
|
+
endColumn: this.location.end.column,
|
|
3533
|
+
message: this.message,
|
|
3534
|
+
severity: 'error'
|
|
3535
|
+
};
|
|
3536
|
+
}
|
|
3537
|
+
treeInspect() {
|
|
3538
|
+
let output = "";
|
|
3539
|
+
output += `@ RenderObjectAndCollectionError ${this.location.treeInspectWithLabel()}\n`;
|
|
3540
|
+
output += `└── message: "${this.message}"\n`;
|
|
3541
|
+
return output;
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
class RenderLayoutWithoutBlockError extends HerbError {
|
|
3545
|
+
layout;
|
|
3546
|
+
static from(data) {
|
|
3547
|
+
return new RenderLayoutWithoutBlockError({
|
|
3548
|
+
type: data.type,
|
|
3549
|
+
message: data.message,
|
|
3550
|
+
location: Location.from(data.location),
|
|
3551
|
+
layout: data.layout,
|
|
3552
|
+
});
|
|
3553
|
+
}
|
|
3554
|
+
constructor(props) {
|
|
3555
|
+
super(props.type, props.message, props.location);
|
|
3556
|
+
this.layout = props.layout;
|
|
3557
|
+
}
|
|
3558
|
+
toJSON() {
|
|
3559
|
+
return {
|
|
3560
|
+
...super.toJSON(),
|
|
3561
|
+
type: "RENDER_LAYOUT_WITHOUT_BLOCK_ERROR",
|
|
3562
|
+
layout: this.layout,
|
|
3563
|
+
};
|
|
3564
|
+
}
|
|
3565
|
+
toMonacoDiagnostic() {
|
|
3566
|
+
return {
|
|
3567
|
+
line: this.location.start.line,
|
|
3568
|
+
column: this.location.start.column,
|
|
3569
|
+
endLine: this.location.end.line,
|
|
3570
|
+
endColumn: this.location.end.column,
|
|
3571
|
+
message: this.message,
|
|
3572
|
+
severity: 'error'
|
|
3573
|
+
};
|
|
3574
|
+
}
|
|
3575
|
+
treeInspect() {
|
|
3576
|
+
let output = "";
|
|
3577
|
+
output += `@ RenderLayoutWithoutBlockError ${this.location.treeInspectWithLabel()}\n`;
|
|
3578
|
+
output += `├── message: "${this.message}"\n`;
|
|
3579
|
+
output += `└── layout: ${JSON.stringify(this.layout)}\n`;
|
|
3580
|
+
return output;
|
|
3581
|
+
}
|
|
3582
|
+
}
|
|
3310
3583
|
function fromSerializedError(error) {
|
|
3311
3584
|
switch (error.type) {
|
|
3312
3585
|
case "UNEXPECTED_ERROR": return UnexpectedError.from(error);
|
|
@@ -3332,6 +3605,13 @@ function fromSerializedError(error) {
|
|
|
3332
3605
|
case "UNCLOSED_ERB_TAG_ERROR": return UnclosedERBTagError.from(error);
|
|
3333
3606
|
case "STRAY_ERB_CLOSING_TAG_ERROR": return StrayERBClosingTagError.from(error);
|
|
3334
3607
|
case "NESTED_ERB_TAG_ERROR": return NestedERBTagError.from(error);
|
|
3608
|
+
case "RENDER_AMBIGUOUS_LOCALS_ERROR": return RenderAmbiguousLocalsError.from(error);
|
|
3609
|
+
case "RENDER_MISSING_LOCALS_ERROR": return RenderMissingLocalsError.from(error);
|
|
3610
|
+
case "RENDER_NO_ARGUMENTS_ERROR": return RenderNoArgumentsError.from(error);
|
|
3611
|
+
case "RENDER_CONFLICTING_PARTIAL_ERROR": return RenderConflictingPartialError.from(error);
|
|
3612
|
+
case "RENDER_INVALID_AS_OPTION_ERROR": return RenderInvalidAsOptionError.from(error);
|
|
3613
|
+
case "RENDER_OBJECT_AND_COLLECTION_ERROR": return RenderObjectAndCollectionError.from(error);
|
|
3614
|
+
case "RENDER_LAYOUT_WITHOUT_BLOCK_ERROR": return RenderLayoutWithoutBlockError.from(error);
|
|
3335
3615
|
default:
|
|
3336
3616
|
throw new Error(`Unknown node type: ${error.type}`);
|
|
3337
3617
|
}
|
|
@@ -23347,7 +23627,7 @@ function deserializePrismNode(bytes, source) {
|
|
|
23347
23627
|
}
|
|
23348
23628
|
|
|
23349
23629
|
// NOTE: This file is generated by the templates/template.rb script and should not
|
|
23350
|
-
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.
|
|
23630
|
+
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.2/templates/javascript/packages/core/src/nodes.ts.erb
|
|
23351
23631
|
class Node {
|
|
23352
23632
|
type;
|
|
23353
23633
|
location;
|
|
@@ -25891,6 +26171,225 @@ class ERBUnlessNode extends Node {
|
|
|
25891
26171
|
return output;
|
|
25892
26172
|
}
|
|
25893
26173
|
}
|
|
26174
|
+
class RubyRenderLocalNode extends Node {
|
|
26175
|
+
name;
|
|
26176
|
+
value;
|
|
26177
|
+
static get type() {
|
|
26178
|
+
return "AST_RUBY_RENDER_LOCAL_NODE";
|
|
26179
|
+
}
|
|
26180
|
+
static from(data) {
|
|
26181
|
+
return new RubyRenderLocalNode({
|
|
26182
|
+
type: data.type,
|
|
26183
|
+
location: Location.from(data.location),
|
|
26184
|
+
errors: (data.errors || []).map(error => HerbError.from(error)),
|
|
26185
|
+
name: data.name ? Token.from(data.name) : null,
|
|
26186
|
+
value: data.value ? fromSerializedNode((data.value)) : null,
|
|
26187
|
+
});
|
|
26188
|
+
}
|
|
26189
|
+
constructor(props) {
|
|
26190
|
+
super(props.type, props.location, props.errors);
|
|
26191
|
+
this.name = props.name;
|
|
26192
|
+
this.value = props.value;
|
|
26193
|
+
}
|
|
26194
|
+
accept(visitor) {
|
|
26195
|
+
visitor.visitRubyRenderLocalNode(this);
|
|
26196
|
+
}
|
|
26197
|
+
childNodes() {
|
|
26198
|
+
return [
|
|
26199
|
+
this.value,
|
|
26200
|
+
];
|
|
26201
|
+
}
|
|
26202
|
+
compactChildNodes() {
|
|
26203
|
+
return this.childNodes().filter(node => node !== null && node !== undefined);
|
|
26204
|
+
}
|
|
26205
|
+
recursiveErrors() {
|
|
26206
|
+
return [
|
|
26207
|
+
...this.errors,
|
|
26208
|
+
this.value ? this.value.recursiveErrors() : [],
|
|
26209
|
+
].flat();
|
|
26210
|
+
}
|
|
26211
|
+
toJSON() {
|
|
26212
|
+
return {
|
|
26213
|
+
...super.toJSON(),
|
|
26214
|
+
type: "AST_RUBY_RENDER_LOCAL_NODE",
|
|
26215
|
+
name: this.name ? this.name.toJSON() : null,
|
|
26216
|
+
value: this.value ? this.value.toJSON() : null,
|
|
26217
|
+
};
|
|
26218
|
+
}
|
|
26219
|
+
treeInspect() {
|
|
26220
|
+
let output = "";
|
|
26221
|
+
output += `@ RubyRenderLocalNode ${this.location.treeInspectWithLabel()}\n`;
|
|
26222
|
+
output += `├── errors: ${this.inspectArray(this.errors, "│ ")}`;
|
|
26223
|
+
output += `├── name: ${this.name ? this.name.treeInspect() : "∅"}\n`;
|
|
26224
|
+
output += `└── value: ${this.inspectNode(this.value, " ")}`;
|
|
26225
|
+
return output;
|
|
26226
|
+
}
|
|
26227
|
+
}
|
|
26228
|
+
class ERBRenderNode extends Node {
|
|
26229
|
+
tag_opening;
|
|
26230
|
+
content;
|
|
26231
|
+
tag_closing;
|
|
26232
|
+
// no-op for analyzed_ruby
|
|
26233
|
+
prism_node;
|
|
26234
|
+
partial;
|
|
26235
|
+
template_path;
|
|
26236
|
+
layout;
|
|
26237
|
+
file;
|
|
26238
|
+
inline_template;
|
|
26239
|
+
body;
|
|
26240
|
+
plain;
|
|
26241
|
+
html;
|
|
26242
|
+
renderable;
|
|
26243
|
+
collection;
|
|
26244
|
+
object;
|
|
26245
|
+
as_name;
|
|
26246
|
+
spacer_template;
|
|
26247
|
+
formats;
|
|
26248
|
+
variants;
|
|
26249
|
+
handlers;
|
|
26250
|
+
content_type;
|
|
26251
|
+
locals;
|
|
26252
|
+
static get type() {
|
|
26253
|
+
return "AST_ERB_RENDER_NODE";
|
|
26254
|
+
}
|
|
26255
|
+
static from(data) {
|
|
26256
|
+
return new ERBRenderNode({
|
|
26257
|
+
type: data.type,
|
|
26258
|
+
location: Location.from(data.location),
|
|
26259
|
+
errors: (data.errors || []).map(error => HerbError.from(error)),
|
|
26260
|
+
tag_opening: data.tag_opening ? Token.from(data.tag_opening) : null,
|
|
26261
|
+
content: data.content ? Token.from(data.content) : null,
|
|
26262
|
+
tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
|
|
26263
|
+
// no-op for analyzed_ruby
|
|
26264
|
+
prism_node: data.prism_node ? new Uint8Array(data.prism_node) : null,
|
|
26265
|
+
partial: data.partial ? Token.from(data.partial) : null,
|
|
26266
|
+
template_path: data.template_path ? Token.from(data.template_path) : null,
|
|
26267
|
+
layout: data.layout ? Token.from(data.layout) : null,
|
|
26268
|
+
file: data.file ? Token.from(data.file) : null,
|
|
26269
|
+
inline_template: data.inline_template ? Token.from(data.inline_template) : null,
|
|
26270
|
+
body: data.body ? Token.from(data.body) : null,
|
|
26271
|
+
plain: data.plain ? Token.from(data.plain) : null,
|
|
26272
|
+
html: data.html ? Token.from(data.html) : null,
|
|
26273
|
+
renderable: data.renderable ? Token.from(data.renderable) : null,
|
|
26274
|
+
collection: data.collection ? Token.from(data.collection) : null,
|
|
26275
|
+
object: data.object ? Token.from(data.object) : null,
|
|
26276
|
+
as_name: data.as_name ? Token.from(data.as_name) : null,
|
|
26277
|
+
spacer_template: data.spacer_template ? Token.from(data.spacer_template) : null,
|
|
26278
|
+
formats: data.formats ? Token.from(data.formats) : null,
|
|
26279
|
+
variants: data.variants ? Token.from(data.variants) : null,
|
|
26280
|
+
handlers: data.handlers ? Token.from(data.handlers) : null,
|
|
26281
|
+
content_type: data.content_type ? Token.from(data.content_type) : null,
|
|
26282
|
+
locals: (data.locals || []).map(node => fromSerializedNode(node)),
|
|
26283
|
+
});
|
|
26284
|
+
}
|
|
26285
|
+
constructor(props) {
|
|
26286
|
+
super(props.type, props.location, props.errors);
|
|
26287
|
+
this.tag_opening = props.tag_opening;
|
|
26288
|
+
this.content = props.content;
|
|
26289
|
+
this.tag_closing = props.tag_closing;
|
|
26290
|
+
// no-op for analyzed_ruby
|
|
26291
|
+
this.prism_node = props.prism_node;
|
|
26292
|
+
this.partial = props.partial;
|
|
26293
|
+
this.template_path = props.template_path;
|
|
26294
|
+
this.layout = props.layout;
|
|
26295
|
+
this.file = props.file;
|
|
26296
|
+
this.inline_template = props.inline_template;
|
|
26297
|
+
this.body = props.body;
|
|
26298
|
+
this.plain = props.plain;
|
|
26299
|
+
this.html = props.html;
|
|
26300
|
+
this.renderable = props.renderable;
|
|
26301
|
+
this.collection = props.collection;
|
|
26302
|
+
this.object = props.object;
|
|
26303
|
+
this.as_name = props.as_name;
|
|
26304
|
+
this.spacer_template = props.spacer_template;
|
|
26305
|
+
this.formats = props.formats;
|
|
26306
|
+
this.variants = props.variants;
|
|
26307
|
+
this.handlers = props.handlers;
|
|
26308
|
+
this.content_type = props.content_type;
|
|
26309
|
+
this.locals = props.locals;
|
|
26310
|
+
}
|
|
26311
|
+
accept(visitor) {
|
|
26312
|
+
visitor.visitERBRenderNode(this);
|
|
26313
|
+
}
|
|
26314
|
+
childNodes() {
|
|
26315
|
+
return [
|
|
26316
|
+
...this.locals,
|
|
26317
|
+
];
|
|
26318
|
+
}
|
|
26319
|
+
compactChildNodes() {
|
|
26320
|
+
return this.childNodes().filter(node => node !== null && node !== undefined);
|
|
26321
|
+
}
|
|
26322
|
+
get prismNode() {
|
|
26323
|
+
if (!this.prism_node || !this.source)
|
|
26324
|
+
return null;
|
|
26325
|
+
return deserializePrismNode(this.prism_node, this.source);
|
|
26326
|
+
}
|
|
26327
|
+
recursiveErrors() {
|
|
26328
|
+
return [
|
|
26329
|
+
...this.errors,
|
|
26330
|
+
...this.locals.map(node => node.recursiveErrors()),
|
|
26331
|
+
].flat();
|
|
26332
|
+
}
|
|
26333
|
+
toJSON() {
|
|
26334
|
+
return {
|
|
26335
|
+
...super.toJSON(),
|
|
26336
|
+
type: "AST_ERB_RENDER_NODE",
|
|
26337
|
+
tag_opening: this.tag_opening ? this.tag_opening.toJSON() : null,
|
|
26338
|
+
content: this.content ? this.content.toJSON() : null,
|
|
26339
|
+
tag_closing: this.tag_closing ? this.tag_closing.toJSON() : null,
|
|
26340
|
+
// no-op for analyzed_ruby
|
|
26341
|
+
prism_node: this.prism_node ? Array.from(this.prism_node) : null,
|
|
26342
|
+
partial: this.partial ? this.partial.toJSON() : null,
|
|
26343
|
+
template_path: this.template_path ? this.template_path.toJSON() : null,
|
|
26344
|
+
layout: this.layout ? this.layout.toJSON() : null,
|
|
26345
|
+
file: this.file ? this.file.toJSON() : null,
|
|
26346
|
+
inline_template: this.inline_template ? this.inline_template.toJSON() : null,
|
|
26347
|
+
body: this.body ? this.body.toJSON() : null,
|
|
26348
|
+
plain: this.plain ? this.plain.toJSON() : null,
|
|
26349
|
+
html: this.html ? this.html.toJSON() : null,
|
|
26350
|
+
renderable: this.renderable ? this.renderable.toJSON() : null,
|
|
26351
|
+
collection: this.collection ? this.collection.toJSON() : null,
|
|
26352
|
+
object: this.object ? this.object.toJSON() : null,
|
|
26353
|
+
as_name: this.as_name ? this.as_name.toJSON() : null,
|
|
26354
|
+
spacer_template: this.spacer_template ? this.spacer_template.toJSON() : null,
|
|
26355
|
+
formats: this.formats ? this.formats.toJSON() : null,
|
|
26356
|
+
variants: this.variants ? this.variants.toJSON() : null,
|
|
26357
|
+
handlers: this.handlers ? this.handlers.toJSON() : null,
|
|
26358
|
+
content_type: this.content_type ? this.content_type.toJSON() : null,
|
|
26359
|
+
locals: this.locals.map(node => node.toJSON()),
|
|
26360
|
+
};
|
|
26361
|
+
}
|
|
26362
|
+
treeInspect() {
|
|
26363
|
+
let output = "";
|
|
26364
|
+
output += `@ ERBRenderNode ${this.location.treeInspectWithLabel()}\n`;
|
|
26365
|
+
output += `├── errors: ${this.inspectArray(this.errors, "│ ")}`;
|
|
26366
|
+
output += `├── tag_opening: ${this.tag_opening ? this.tag_opening.treeInspect() : "∅"}\n`;
|
|
26367
|
+
output += `├── content: ${this.content ? this.content.treeInspect() : "∅"}\n`;
|
|
26368
|
+
output += `├── tag_closing: ${this.tag_closing ? this.tag_closing.treeInspect() : "∅"}\n`;
|
|
26369
|
+
if (this.prism_node) {
|
|
26370
|
+
output += `├── prism_node: ${this.source ? inspectPrismSerialized(this.prism_node, this.source, "│ ") : `(${this.prism_node.length} bytes)`}\n`;
|
|
26371
|
+
}
|
|
26372
|
+
output += `├── partial: ${this.partial ? this.partial.treeInspect() : "∅"}\n`;
|
|
26373
|
+
output += `├── template_path: ${this.template_path ? this.template_path.treeInspect() : "∅"}\n`;
|
|
26374
|
+
output += `├── layout: ${this.layout ? this.layout.treeInspect() : "∅"}\n`;
|
|
26375
|
+
output += `├── file: ${this.file ? this.file.treeInspect() : "∅"}\n`;
|
|
26376
|
+
output += `├── inline_template: ${this.inline_template ? this.inline_template.treeInspect() : "∅"}\n`;
|
|
26377
|
+
output += `├── body: ${this.body ? this.body.treeInspect() : "∅"}\n`;
|
|
26378
|
+
output += `├── plain: ${this.plain ? this.plain.treeInspect() : "∅"}\n`;
|
|
26379
|
+
output += `├── html: ${this.html ? this.html.treeInspect() : "∅"}\n`;
|
|
26380
|
+
output += `├── renderable: ${this.renderable ? this.renderable.treeInspect() : "∅"}\n`;
|
|
26381
|
+
output += `├── collection: ${this.collection ? this.collection.treeInspect() : "∅"}\n`;
|
|
26382
|
+
output += `├── object: ${this.object ? this.object.treeInspect() : "∅"}\n`;
|
|
26383
|
+
output += `├── as_name: ${this.as_name ? this.as_name.treeInspect() : "∅"}\n`;
|
|
26384
|
+
output += `├── spacer_template: ${this.spacer_template ? this.spacer_template.treeInspect() : "∅"}\n`;
|
|
26385
|
+
output += `├── formats: ${this.formats ? this.formats.treeInspect() : "∅"}\n`;
|
|
26386
|
+
output += `├── variants: ${this.variants ? this.variants.treeInspect() : "∅"}\n`;
|
|
26387
|
+
output += `├── handlers: ${this.handlers ? this.handlers.treeInspect() : "∅"}\n`;
|
|
26388
|
+
output += `├── content_type: ${this.content_type ? this.content_type.treeInspect() : "∅"}\n`;
|
|
26389
|
+
output += `└── locals: ${this.inspectArray(this.locals, " ")}`;
|
|
26390
|
+
return output;
|
|
26391
|
+
}
|
|
26392
|
+
}
|
|
25894
26393
|
class ERBYieldNode extends Node {
|
|
25895
26394
|
tag_opening;
|
|
25896
26395
|
content;
|
|
@@ -26054,6 +26553,8 @@ function fromSerializedNode(node) {
|
|
|
26054
26553
|
case "AST_ERB_ENSURE_NODE": return ERBEnsureNode.from(node);
|
|
26055
26554
|
case "AST_ERB_BEGIN_NODE": return ERBBeginNode.from(node);
|
|
26056
26555
|
case "AST_ERB_UNLESS_NODE": return ERBUnlessNode.from(node);
|
|
26556
|
+
case "AST_RUBY_RENDER_LOCAL_NODE": return RubyRenderLocalNode.from(node);
|
|
26557
|
+
case "AST_ERB_RENDER_NODE": return ERBRenderNode.from(node);
|
|
26057
26558
|
case "AST_ERB_YIELD_NODE": return ERBYieldNode.from(node);
|
|
26058
26559
|
case "AST_ERB_IN_NODE": return ERBInNode.from(node);
|
|
26059
26560
|
default:
|
|
@@ -26103,6 +26604,7 @@ const DEFAULT_PARSER_OPTIONS = {
|
|
|
26103
26604
|
analyze: true,
|
|
26104
26605
|
strict: true,
|
|
26105
26606
|
action_view_helpers: false,
|
|
26607
|
+
render_nodes: false,
|
|
26106
26608
|
prism_nodes: false,
|
|
26107
26609
|
prism_nodes_deep: false,
|
|
26108
26610
|
prism_program: false,
|
|
@@ -26119,6 +26621,8 @@ class ParserOptions {
|
|
|
26119
26621
|
analyze;
|
|
26120
26622
|
/** Whether ActionView tag helper transformation was enabled during parsing. */
|
|
26121
26623
|
action_view_helpers;
|
|
26624
|
+
/** Whether ActionView render call detection was enabled during parsing. */
|
|
26625
|
+
render_nodes;
|
|
26122
26626
|
/** Whether Prism node serialization was enabled during parsing. */
|
|
26123
26627
|
prism_nodes;
|
|
26124
26628
|
/** Whether deep Prism node serialization was enabled during parsing. */
|
|
@@ -26133,6 +26637,7 @@ class ParserOptions {
|
|
|
26133
26637
|
this.track_whitespace = options.track_whitespace ?? DEFAULT_PARSER_OPTIONS.track_whitespace;
|
|
26134
26638
|
this.analyze = options.analyze ?? DEFAULT_PARSER_OPTIONS.analyze;
|
|
26135
26639
|
this.action_view_helpers = options.action_view_helpers ?? DEFAULT_PARSER_OPTIONS.action_view_helpers;
|
|
26640
|
+
this.render_nodes = options.render_nodes ?? DEFAULT_PARSER_OPTIONS.render_nodes;
|
|
26136
26641
|
this.prism_nodes = options.prism_nodes ?? DEFAULT_PARSER_OPTIONS.prism_nodes;
|
|
26137
26642
|
this.prism_nodes_deep = options.prism_nodes_deep ?? DEFAULT_PARSER_OPTIONS.prism_nodes_deep;
|
|
26138
26643
|
this.prism_program = options.prism_program ?? DEFAULT_PARSER_OPTIONS.prism_program;
|
|
@@ -26212,7 +26717,7 @@ class ParseResult extends Result {
|
|
|
26212
26717
|
}
|
|
26213
26718
|
|
|
26214
26719
|
// NOTE: This file is generated by the templates/template.rb script and should not
|
|
26215
|
-
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.
|
|
26720
|
+
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.2/templates/javascript/packages/core/src/node-type-guards.ts.erb
|
|
26216
26721
|
/**
|
|
26217
26722
|
* Type guard functions for AST nodes.
|
|
26218
26723
|
* These functions provide type checking by combining both instanceof
|
|
@@ -26507,6 +27012,22 @@ function isERBUnlessNode(node) {
|
|
|
26507
27012
|
return false;
|
|
26508
27013
|
return node instanceof ERBUnlessNode || node.type === "AST_ERB_UNLESS_NODE" || node.constructor.type === "AST_ERB_UNLESS_NODE";
|
|
26509
27014
|
}
|
|
27015
|
+
/**
|
|
27016
|
+
* Checks if a node is a RubyRenderLocalNode
|
|
27017
|
+
*/
|
|
27018
|
+
function isRubyRenderLocalNode(node) {
|
|
27019
|
+
if (!node)
|
|
27020
|
+
return false;
|
|
27021
|
+
return node instanceof RubyRenderLocalNode || node.type === "AST_RUBY_RENDER_LOCAL_NODE" || node.constructor.type === "AST_RUBY_RENDER_LOCAL_NODE";
|
|
27022
|
+
}
|
|
27023
|
+
/**
|
|
27024
|
+
* Checks if a node is a ERBRenderNode
|
|
27025
|
+
*/
|
|
27026
|
+
function isERBRenderNode(node) {
|
|
27027
|
+
if (!node)
|
|
27028
|
+
return false;
|
|
27029
|
+
return node instanceof ERBRenderNode || node.type === "AST_ERB_RENDER_NODE" || node.constructor.type === "AST_ERB_RENDER_NODE";
|
|
27030
|
+
}
|
|
26510
27031
|
/**
|
|
26511
27032
|
* Checks if a node is a ERBYieldNode
|
|
26512
27033
|
*/
|
|
@@ -26543,6 +27064,7 @@ function isERBNode(node) {
|
|
|
26543
27064
|
isERBEnsureNode(node) ||
|
|
26544
27065
|
isERBBeginNode(node) ||
|
|
26545
27066
|
isERBUnlessNode(node) ||
|
|
27067
|
+
isERBRenderNode(node) ||
|
|
26546
27068
|
isERBYieldNode(node) ||
|
|
26547
27069
|
isERBInNode(node);
|
|
26548
27070
|
}
|
|
@@ -26593,6 +27115,8 @@ const NODE_TYPE_GUARDS = new Map([
|
|
|
26593
27115
|
[ERBEnsureNode, isERBEnsureNode],
|
|
26594
27116
|
[ERBBeginNode, isERBBeginNode],
|
|
26595
27117
|
[ERBUnlessNode, isERBUnlessNode],
|
|
27118
|
+
[RubyRenderLocalNode, isRubyRenderLocalNode],
|
|
27119
|
+
[ERBRenderNode, isERBRenderNode],
|
|
26596
27120
|
[ERBYieldNode, isERBYieldNode],
|
|
26597
27121
|
[ERBInNode, isERBInNode],
|
|
26598
27122
|
]);
|
|
@@ -26643,6 +27167,8 @@ const AST_TYPE_GUARDS = new Map([
|
|
|
26643
27167
|
["AST_ERB_ENSURE_NODE", isERBEnsureNode],
|
|
26644
27168
|
["AST_ERB_BEGIN_NODE", isERBBeginNode],
|
|
26645
27169
|
["AST_ERB_UNLESS_NODE", isERBUnlessNode],
|
|
27170
|
+
["AST_RUBY_RENDER_LOCAL_NODE", isRubyRenderLocalNode],
|
|
27171
|
+
["AST_ERB_RENDER_NODE", isERBRenderNode],
|
|
26646
27172
|
["AST_ERB_YIELD_NODE", isERBYieldNode],
|
|
26647
27173
|
["AST_ERB_IN_NODE", isERBInNode],
|
|
26648
27174
|
]);
|
|
@@ -26789,6 +27315,12 @@ function getStaticContentFromNodes(nodes) {
|
|
|
26789
27315
|
}
|
|
26790
27316
|
return literalNodes.map(node => node.content).join("");
|
|
26791
27317
|
}
|
|
27318
|
+
/**
|
|
27319
|
+
* Checks if nodes contain any literal content (for static validation)
|
|
27320
|
+
*/
|
|
27321
|
+
function hasStaticContent(nodes) {
|
|
27322
|
+
return nodes.some(isLiteralNode);
|
|
27323
|
+
}
|
|
26792
27324
|
/**
|
|
26793
27325
|
* Checks if nodes are effectively static (only literals and non-output ERB)
|
|
26794
27326
|
* Non-output ERB like <% if %> doesn't affect static validation
|
|
@@ -26831,7 +27363,7 @@ function getCombinedStringFromNodes(nodes) {
|
|
|
26831
27363
|
/**
|
|
26832
27364
|
* Checks if an HTML attribute name node has dynamic content (contains ERB)
|
|
26833
27365
|
*/
|
|
26834
|
-
function
|
|
27366
|
+
function hasDynamicAttributeNameNode(attributeNameNode) {
|
|
26835
27367
|
if (!attributeNameNode.children) {
|
|
26836
27368
|
return false;
|
|
26837
27369
|
}
|
|
@@ -26905,7 +27437,9 @@ function getAttributeName(attributeNode, lowercase = true) {
|
|
|
26905
27437
|
return staticName ? staticName.toLowerCase() : null;
|
|
26906
27438
|
}
|
|
26907
27439
|
function hasStaticAttributeValue(nodeOrAttribute, attributeName) {
|
|
26908
|
-
const attributeNode =
|
|
27440
|
+
const attributeNode = attributeName
|
|
27441
|
+
? getAttribute(nodeOrAttribute, attributeName)
|
|
27442
|
+
: nodeOrAttribute;
|
|
26909
27443
|
if (!attributeNode?.value?.children)
|
|
26910
27444
|
return false;
|
|
26911
27445
|
return attributeNode.value.children.every(isLiteralNode);
|
|
@@ -26964,12 +27498,11 @@ function hasAttribute(node, attributeName) {
|
|
|
26964
27498
|
}
|
|
26965
27499
|
/**
|
|
26966
27500
|
* Checks if an attribute has a dynamic (ERB-containing) name.
|
|
26967
|
-
* Accepts an HTMLAttributeNode (wraps the core HTMLAttributeNameNode-level check).
|
|
26968
27501
|
*/
|
|
26969
|
-
function
|
|
27502
|
+
function hasDynamicAttributeName(attributeNode) {
|
|
26970
27503
|
if (!isHTMLAttributeNameNode(attributeNode.name))
|
|
26971
27504
|
return false;
|
|
26972
|
-
return
|
|
27505
|
+
return hasDynamicAttributeNameNode(attributeNode.name);
|
|
26973
27506
|
}
|
|
26974
27507
|
/**
|
|
26975
27508
|
* Gets the combined string representation of an attribute name (including ERB syntax).
|
|
@@ -26980,12 +27513,34 @@ function getCombinedAttributeNameString(attributeNode) {
|
|
|
26980
27513
|
return "";
|
|
26981
27514
|
return getCombinedAttributeName(attributeNode.name);
|
|
26982
27515
|
}
|
|
27516
|
+
/**
|
|
27517
|
+
* Checks if an attribute value contains dynamic content (ERB)
|
|
27518
|
+
*/
|
|
27519
|
+
function hasDynamicAttributeValue(attributeNode) {
|
|
27520
|
+
if (!attributeNode.value?.children)
|
|
27521
|
+
return false;
|
|
27522
|
+
return attributeNode.value.children.some(isERBContentNode);
|
|
27523
|
+
}
|
|
26983
27524
|
/**
|
|
26984
27525
|
* Gets the value nodes array from an attribute for dynamic inspection
|
|
26985
27526
|
*/
|
|
26986
27527
|
function getAttributeValueNodes(attributeNode) {
|
|
26987
27528
|
return attributeNode.value?.children || [];
|
|
26988
27529
|
}
|
|
27530
|
+
/**
|
|
27531
|
+
* Checks if an attribute value contains any static content (for validation purposes)
|
|
27532
|
+
*/
|
|
27533
|
+
function hasStaticAttributeValueContent(attributeNode) {
|
|
27534
|
+
return hasStaticContent(getAttributeValueNodes(attributeNode));
|
|
27535
|
+
}
|
|
27536
|
+
/**
|
|
27537
|
+
* Gets the static content of an attribute value (all literal parts combined).
|
|
27538
|
+
* Unlike getStaticAttributeValue, this extracts only the static portions from mixed content.
|
|
27539
|
+
* Returns the concatenated literal content, or null if no literal nodes exist.
|
|
27540
|
+
*/
|
|
27541
|
+
function getStaticAttributeValueContent(attributeNode) {
|
|
27542
|
+
return getStaticContentFromNodes(getAttributeValueNodes(attributeNode));
|
|
27543
|
+
}
|
|
26989
27544
|
/**
|
|
26990
27545
|
* Gets the combined attribute value including both static text and ERB tag syntax.
|
|
26991
27546
|
* For ERB nodes, includes the full tag syntax (e.g., "<%= foo %>").
|
|
@@ -27029,6 +27584,14 @@ function getAttributeValueQuoteType(node) {
|
|
|
27029
27584
|
}
|
|
27030
27585
|
return "none";
|
|
27031
27586
|
}
|
|
27587
|
+
/**
|
|
27588
|
+
* Checks if an attribute value is quoted
|
|
27589
|
+
*/
|
|
27590
|
+
function isAttributeValueQuoted(attributeNode) {
|
|
27591
|
+
if (!isHTMLAttributeValueNode(attributeNode.value))
|
|
27592
|
+
return false;
|
|
27593
|
+
return !!attributeNode.value.quoted;
|
|
27594
|
+
}
|
|
27032
27595
|
/**
|
|
27033
27596
|
* Iterates over all attributes of an element or open tag node
|
|
27034
27597
|
*/
|
|
@@ -27322,6 +27885,19 @@ function createWhitespaceNode() {
|
|
|
27322
27885
|
});
|
|
27323
27886
|
}
|
|
27324
27887
|
|
|
27888
|
+
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#boolean-attributes
|
|
27889
|
+
const HTML_BOOLEAN_ATTRIBUTES = new Set([
|
|
27890
|
+
"allowfullscreen", "async", "autofocus", "autoplay", "checked", "compact",
|
|
27891
|
+
"controls", "declare", "default", "defer", "disabled", "formnovalidate",
|
|
27892
|
+
"hidden", "inert", "ismap", "itemscope", "loop", "multiple", "muted",
|
|
27893
|
+
"nomodule", "nohref", "noresize", "noshade", "novalidate", "nowrap",
|
|
27894
|
+
"open", "playsinline", "readonly", "required", "reversed", "scoped",
|
|
27895
|
+
"seamless", "selected", "sortable", "truespeed", "typemustmatch",
|
|
27896
|
+
]);
|
|
27897
|
+
function isBooleanAttribute(attributeName) {
|
|
27898
|
+
return HTML_BOOLEAN_ATTRIBUTES.has(attributeName.toLowerCase());
|
|
27899
|
+
}
|
|
27900
|
+
|
|
27325
27901
|
/*
|
|
27326
27902
|
* The following code is derived from the "js-levenshtein" repository,
|
|
27327
27903
|
* Copyright (c) 2017 Gustaf Andersson (https://github.com/gustf/js-levenshtein)
|
|
@@ -27468,7 +28044,7 @@ function didyoumean(input, list, threshold) {
|
|
|
27468
28044
|
}
|
|
27469
28045
|
|
|
27470
28046
|
// NOTE: This file is generated by the templates/template.rb script and should not
|
|
27471
|
-
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.
|
|
28047
|
+
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.2/templates/javascript/packages/core/src/visitor.ts.erb
|
|
27472
28048
|
class Visitor {
|
|
27473
28049
|
visit(node) {
|
|
27474
28050
|
if (!node)
|
|
@@ -27647,6 +28223,15 @@ class Visitor {
|
|
|
27647
28223
|
this.visitERBNode(node);
|
|
27648
28224
|
this.visitChildNodes(node);
|
|
27649
28225
|
}
|
|
28226
|
+
visitRubyRenderLocalNode(node) {
|
|
28227
|
+
this.visitNode(node);
|
|
28228
|
+
this.visitChildNodes(node);
|
|
28229
|
+
}
|
|
28230
|
+
visitERBRenderNode(node) {
|
|
28231
|
+
this.visitNode(node);
|
|
28232
|
+
this.visitERBNode(node);
|
|
28233
|
+
this.visitChildNodes(node);
|
|
28234
|
+
}
|
|
27650
28235
|
visitERBYieldNode(node) {
|
|
27651
28236
|
this.visitNode(node);
|
|
27652
28237
|
this.visitERBNode(node);
|
|
@@ -28097,6 +28682,12 @@ class IdentityPrinter extends Printer {
|
|
|
28097
28682
|
this.visit(node.end_node);
|
|
28098
28683
|
}
|
|
28099
28684
|
}
|
|
28685
|
+
visitERBRenderNode(node) {
|
|
28686
|
+
this.printERBNode(node);
|
|
28687
|
+
}
|
|
28688
|
+
visitRubyRenderLocalNode(_node) {
|
|
28689
|
+
// extracted metadata, nothing to print
|
|
28690
|
+
}
|
|
28100
28691
|
visitERBYieldNode(node) {
|
|
28101
28692
|
this.printERBNode(node);
|
|
28102
28693
|
}
|
|
@@ -28647,7 +29238,7 @@ class ParserRule {
|
|
|
28647
29238
|
get parserOptions() {
|
|
28648
29239
|
return DEFAULT_LINTER_PARSER_OPTIONS;
|
|
28649
29240
|
}
|
|
28650
|
-
createOffense(message, location, autofixContext, severity) {
|
|
29241
|
+
createOffense(message, location, autofixContext, severity, tags) {
|
|
28651
29242
|
return {
|
|
28652
29243
|
rule: this.ruleName,
|
|
28653
29244
|
code: this.ruleName,
|
|
@@ -28656,6 +29247,7 @@ class ParserRule {
|
|
|
28656
29247
|
location,
|
|
28657
29248
|
autofixContext,
|
|
28658
29249
|
severity,
|
|
29250
|
+
tags,
|
|
28659
29251
|
};
|
|
28660
29252
|
}
|
|
28661
29253
|
}
|
|
@@ -28675,7 +29267,7 @@ class LexerRule {
|
|
|
28675
29267
|
get defaultConfig() {
|
|
28676
29268
|
return DEFAULT_RULE_CONFIG;
|
|
28677
29269
|
}
|
|
28678
|
-
createOffense(message, location, autofixContext, severity) {
|
|
29270
|
+
createOffense(message, location, autofixContext, severity, tags) {
|
|
28679
29271
|
return {
|
|
28680
29272
|
rule: this.ruleName,
|
|
28681
29273
|
code: this.ruleName,
|
|
@@ -28684,6 +29276,7 @@ class LexerRule {
|
|
|
28684
29276
|
location,
|
|
28685
29277
|
autofixContext,
|
|
28686
29278
|
severity,
|
|
29279
|
+
tags,
|
|
28687
29280
|
};
|
|
28688
29281
|
}
|
|
28689
29282
|
}
|
|
@@ -28709,7 +29302,7 @@ class SourceRule {
|
|
|
28709
29302
|
get defaultConfig() {
|
|
28710
29303
|
return DEFAULT_RULE_CONFIG;
|
|
28711
29304
|
}
|
|
28712
|
-
createOffense(message, location, autofixContext, severity) {
|
|
29305
|
+
createOffense(message, location, autofixContext, severity, tags) {
|
|
28713
29306
|
return {
|
|
28714
29307
|
rule: this.ruleName,
|
|
28715
29308
|
code: this.ruleName,
|
|
@@ -28718,6 +29311,7 @@ class SourceRule {
|
|
|
28718
29311
|
location,
|
|
28719
29312
|
autofixContext,
|
|
28720
29313
|
severity,
|
|
29314
|
+
tags,
|
|
28721
29315
|
};
|
|
28722
29316
|
}
|
|
28723
29317
|
}
|
|
@@ -28743,7 +29337,7 @@ class BaseRuleVisitor extends Visitor {
|
|
|
28743
29337
|
* Helper method to create an unbound lint offense (without severity).
|
|
28744
29338
|
* The Linter will bind severity based on the rule's config.
|
|
28745
29339
|
*/
|
|
28746
|
-
createOffense(message, location, autofixContext, severity) {
|
|
29340
|
+
createOffense(message, location, autofixContext, severity, tags) {
|
|
28747
29341
|
return {
|
|
28748
29342
|
rule: this.ruleName,
|
|
28749
29343
|
code: this.ruleName,
|
|
@@ -28752,13 +29346,14 @@ class BaseRuleVisitor extends Visitor {
|
|
|
28752
29346
|
location,
|
|
28753
29347
|
autofixContext,
|
|
28754
29348
|
severity,
|
|
29349
|
+
tags,
|
|
28755
29350
|
};
|
|
28756
29351
|
}
|
|
28757
29352
|
/**
|
|
28758
29353
|
* Helper method to add an offense to the offenses array
|
|
28759
29354
|
*/
|
|
28760
|
-
addOffense(message, location, autofixContext, severity) {
|
|
28761
|
-
this.offenses.push(this.createOffense(message, location, autofixContext, severity));
|
|
29355
|
+
addOffense(message, location, autofixContext, severity, tags) {
|
|
29356
|
+
this.offenses.push(this.createOffense(message, location, autofixContext, severity, tags));
|
|
28762
29357
|
}
|
|
28763
29358
|
}
|
|
28764
29359
|
/**
|
|
@@ -28845,13 +29440,6 @@ const HTML_VOID_ELEMENTS = new Set([
|
|
|
28845
29440
|
"area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta",
|
|
28846
29441
|
"param", "source", "track", "wbr",
|
|
28847
29442
|
]);
|
|
28848
|
-
const HTML_BOOLEAN_ATTRIBUTES = new Set([
|
|
28849
|
-
"autofocus", "autoplay", "checked", "controls", "defer", "disabled", "hidden",
|
|
28850
|
-
"loop", "multiple", "muted", "readonly", "required", "reversed", "selected",
|
|
28851
|
-
"open", "default", "formnovalidate", "novalidate", "itemscope", "scoped",
|
|
28852
|
-
"seamless", "allowfullscreen", "async", "compact", "declare", "nohref",
|
|
28853
|
-
"noresize", "noshade", "nowrap", "sortable", "truespeed", "typemustmatch"
|
|
28854
|
-
]);
|
|
28855
29443
|
const HEADING_TAGS = new Set(["h1", "h2", "h3", "h4", "h5", "h6"]);
|
|
28856
29444
|
/**
|
|
28857
29445
|
* SVG elements that use camelCase naming
|
|
@@ -29006,12 +29594,6 @@ function isBlockElement(tagName) {
|
|
|
29006
29594
|
function isVoidElement(tagName) {
|
|
29007
29595
|
return HTML_VOID_ELEMENTS.has(tagName.toLowerCase());
|
|
29008
29596
|
}
|
|
29009
|
-
/**
|
|
29010
|
-
* Checks if an attribute is a boolean attribute
|
|
29011
|
-
*/
|
|
29012
|
-
function isBooleanAttribute(attributeName) {
|
|
29013
|
-
return HTML_BOOLEAN_ATTRIBUTES.has(attributeName.toLowerCase());
|
|
29014
|
-
}
|
|
29015
29597
|
/**
|
|
29016
29598
|
* Attribute visitor that provides granular processing based on both
|
|
29017
29599
|
* attribute name type (static/dynamic) and value type (static/dynamic)
|
|
@@ -29034,7 +29616,7 @@ class AttributeVisitorMixin extends BaseRuleVisitor {
|
|
|
29034
29616
|
forEachAttribute(node, (attributeNode) => {
|
|
29035
29617
|
const staticAttributeName = getAttributeName(attributeNode);
|
|
29036
29618
|
const originalAttributeName = getAttributeName(attributeNode, false) || "";
|
|
29037
|
-
const isDynamicName =
|
|
29619
|
+
const isDynamicName = hasDynamicAttributeName(attributeNode);
|
|
29038
29620
|
const staticAttributeValue = getStaticAttributeValue(attributeNode);
|
|
29039
29621
|
const valueNodes = getAttributeValueNodes(attributeNode);
|
|
29040
29622
|
const hasOutputERB = hasERBOutput(valueNodes);
|
|
@@ -29111,7 +29693,7 @@ class BaseLexerRuleVisitor {
|
|
|
29111
29693
|
* Helper method to create an unbound lint offense (without severity).
|
|
29112
29694
|
* The Linter will bind severity based on the rule's config.
|
|
29113
29695
|
*/
|
|
29114
|
-
createOffense(message, location, autofixContext, severity) {
|
|
29696
|
+
createOffense(message, location, autofixContext, severity, tags) {
|
|
29115
29697
|
return {
|
|
29116
29698
|
rule: this.ruleName,
|
|
29117
29699
|
code: this.ruleName,
|
|
@@ -29120,13 +29702,14 @@ class BaseLexerRuleVisitor {
|
|
|
29120
29702
|
location,
|
|
29121
29703
|
autofixContext,
|
|
29122
29704
|
severity,
|
|
29705
|
+
tags,
|
|
29123
29706
|
};
|
|
29124
29707
|
}
|
|
29125
29708
|
/**
|
|
29126
29709
|
* Helper method to add an offense to the offenses array
|
|
29127
29710
|
*/
|
|
29128
|
-
addOffense(message, location, autofixContext, severity) {
|
|
29129
|
-
this.offenses.push(this.createOffense(message, location, autofixContext, severity));
|
|
29711
|
+
addOffense(message, location, autofixContext, severity, tags) {
|
|
29712
|
+
this.offenses.push(this.createOffense(message, location, autofixContext, severity, tags));
|
|
29130
29713
|
}
|
|
29131
29714
|
/**
|
|
29132
29715
|
* Main entry point for lexer rule visitors
|
|
@@ -29167,7 +29750,7 @@ class BaseSourceRuleVisitor {
|
|
|
29167
29750
|
* Helper method to create an unbound lint offense (without severity).
|
|
29168
29751
|
* The Linter will bind severity based on the rule's config.
|
|
29169
29752
|
*/
|
|
29170
|
-
createOffense(message, location, autofixContext, severity) {
|
|
29753
|
+
createOffense(message, location, autofixContext, severity, tags) {
|
|
29171
29754
|
return {
|
|
29172
29755
|
rule: this.ruleName,
|
|
29173
29756
|
code: this.ruleName,
|
|
@@ -29176,13 +29759,14 @@ class BaseSourceRuleVisitor {
|
|
|
29176
29759
|
location,
|
|
29177
29760
|
autofixContext,
|
|
29178
29761
|
severity,
|
|
29762
|
+
tags,
|
|
29179
29763
|
};
|
|
29180
29764
|
}
|
|
29181
29765
|
/**
|
|
29182
29766
|
* Helper method to add an offense to the offenses array
|
|
29183
29767
|
*/
|
|
29184
|
-
addOffense(message, location, autofixContext, severity) {
|
|
29185
|
-
this.offenses.push(this.createOffense(message, location, autofixContext, severity));
|
|
29768
|
+
addOffense(message, location, autofixContext, severity, tags) {
|
|
29769
|
+
this.offenses.push(this.createOffense(message, location, autofixContext, severity, tags));
|
|
29186
29770
|
}
|
|
29187
29771
|
/**
|
|
29188
29772
|
* Main entry point for source rule visitors
|
|
@@ -29532,6 +30116,34 @@ class ActionViewNoSilentHelperRule extends ParserRule {
|
|
|
29532
30116
|
}
|
|
29533
30117
|
}
|
|
29534
30118
|
|
|
30119
|
+
class ActionViewNoSilentRenderVisitor extends BaseRuleVisitor {
|
|
30120
|
+
visitERBRenderNode(node) {
|
|
30121
|
+
if (!isERBOutputNode(node)) {
|
|
30122
|
+
this.addOffense(`Avoid using \`${node.tag_opening?.value} %>\` with \`render\`. Use \`<%= %>\` to ensure the rendered content is output.`, node.location);
|
|
30123
|
+
}
|
|
30124
|
+
this.visitChildNodes(node);
|
|
30125
|
+
}
|
|
30126
|
+
}
|
|
30127
|
+
class ActionViewNoSilentRenderRule extends ParserRule {
|
|
30128
|
+
static ruleName = "actionview-no-silent-render";
|
|
30129
|
+
get defaultConfig() {
|
|
30130
|
+
return {
|
|
30131
|
+
enabled: true,
|
|
30132
|
+
severity: "error"
|
|
30133
|
+
};
|
|
30134
|
+
}
|
|
30135
|
+
get parserOptions() {
|
|
30136
|
+
return {
|
|
30137
|
+
render_nodes: true,
|
|
30138
|
+
};
|
|
30139
|
+
}
|
|
30140
|
+
check(result, context) {
|
|
30141
|
+
const visitor = new ActionViewNoSilentRenderVisitor(this.ruleName, context);
|
|
30142
|
+
visitor.visit(result.value);
|
|
30143
|
+
return visitor.offenses;
|
|
30144
|
+
}
|
|
30145
|
+
}
|
|
30146
|
+
|
|
29535
30147
|
class ERBCommentSyntaxVisitor extends BaseRuleVisitor {
|
|
29536
30148
|
visitERBContentNode(node) {
|
|
29537
30149
|
const content = node.content?.value || "";
|
|
@@ -29596,7 +30208,9 @@ class ERBNoCaseNodeChildrenVisitor extends BaseRuleVisitor {
|
|
|
29596
30208
|
for (const child of node.children) {
|
|
29597
30209
|
if (!this.isAllowedContent(child)) {
|
|
29598
30210
|
const childCode = IdentityPrinter.print(child).trim();
|
|
29599
|
-
this.
|
|
30211
|
+
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);
|
|
30212
|
+
offense.tags = ["unnecessary"];
|
|
30213
|
+
this.offenses.push(offense);
|
|
29600
30214
|
}
|
|
29601
30215
|
}
|
|
29602
30216
|
}
|
|
@@ -29626,6 +30240,193 @@ class ERBNoCaseNodeChildrenRule extends ParserRule {
|
|
|
29626
30240
|
}
|
|
29627
30241
|
}
|
|
29628
30242
|
|
|
30243
|
+
class ERBNoEmptyControlFlowVisitor extends BaseRuleVisitor {
|
|
30244
|
+
processedIfNodes = new Set();
|
|
30245
|
+
processedElseNodes = new Set();
|
|
30246
|
+
visitERBIfNode(node) {
|
|
30247
|
+
if (this.processedIfNodes.has(node)) {
|
|
30248
|
+
return;
|
|
30249
|
+
}
|
|
30250
|
+
this.markIfChainAsProcessed(node);
|
|
30251
|
+
this.markElseNodesInIfChain(node);
|
|
30252
|
+
const entireChainEmpty = this.isEntireIfChainEmpty(node);
|
|
30253
|
+
if (entireChainEmpty) {
|
|
30254
|
+
this.addEmptyBlockOffense(node, node.statements, "if");
|
|
30255
|
+
}
|
|
30256
|
+
else {
|
|
30257
|
+
this.checkIfChainParts(node);
|
|
30258
|
+
}
|
|
30259
|
+
this.visitChildNodes(node);
|
|
30260
|
+
}
|
|
30261
|
+
visitERBElseNode(node) {
|
|
30262
|
+
if (this.processedElseNodes.has(node)) {
|
|
30263
|
+
this.visitChildNodes(node);
|
|
30264
|
+
return;
|
|
30265
|
+
}
|
|
30266
|
+
this.addEmptyBlockOffense(node, node.statements, "else");
|
|
30267
|
+
this.visitChildNodes(node);
|
|
30268
|
+
}
|
|
30269
|
+
visitERBUnlessNode(node) {
|
|
30270
|
+
const unlessHasContent = this.statementsHaveContent(node.statements);
|
|
30271
|
+
const elseHasContent = node.else_clause && this.statementsHaveContent(node.else_clause.statements);
|
|
30272
|
+
if (node.else_clause) {
|
|
30273
|
+
this.processedElseNodes.add(node.else_clause);
|
|
30274
|
+
}
|
|
30275
|
+
const entireBlockEmpty = !unlessHasContent && !elseHasContent;
|
|
30276
|
+
if (entireBlockEmpty) {
|
|
30277
|
+
this.addEmptyBlockOffense(node, node.statements, "unless");
|
|
30278
|
+
}
|
|
30279
|
+
else {
|
|
30280
|
+
if (!unlessHasContent) {
|
|
30281
|
+
this.addEmptyBlockOffenseWithEnd(node, node.statements, "unless", node.else_clause);
|
|
30282
|
+
}
|
|
30283
|
+
if (node.else_clause && !elseHasContent) {
|
|
30284
|
+
this.addEmptyBlockOffense(node.else_clause, node.else_clause.statements, "else");
|
|
30285
|
+
}
|
|
30286
|
+
}
|
|
30287
|
+
this.visitChildNodes(node);
|
|
30288
|
+
}
|
|
30289
|
+
visitERBForNode(node) {
|
|
30290
|
+
this.addEmptyBlockOffense(node, node.statements, "for");
|
|
30291
|
+
this.visitChildNodes(node);
|
|
30292
|
+
}
|
|
30293
|
+
visitERBWhileNode(node) {
|
|
30294
|
+
this.addEmptyBlockOffense(node, node.statements, "while");
|
|
30295
|
+
this.visitChildNodes(node);
|
|
30296
|
+
}
|
|
30297
|
+
visitERBUntilNode(node) {
|
|
30298
|
+
this.addEmptyBlockOffense(node, node.statements, "until");
|
|
30299
|
+
this.visitChildNodes(node);
|
|
30300
|
+
}
|
|
30301
|
+
visitERBWhenNode(node) {
|
|
30302
|
+
if (!node.then_keyword) {
|
|
30303
|
+
this.addEmptyBlockOffense(node, node.statements, "when");
|
|
30304
|
+
}
|
|
30305
|
+
this.visitChildNodes(node);
|
|
30306
|
+
}
|
|
30307
|
+
visitERBInNode(node) {
|
|
30308
|
+
if (!node.then_keyword) {
|
|
30309
|
+
this.addEmptyBlockOffense(node, node.statements, "in");
|
|
30310
|
+
}
|
|
30311
|
+
this.visitChildNodes(node);
|
|
30312
|
+
}
|
|
30313
|
+
visitERBBeginNode(node) {
|
|
30314
|
+
this.addEmptyBlockOffense(node, node.statements, "begin");
|
|
30315
|
+
this.visitChildNodes(node);
|
|
30316
|
+
}
|
|
30317
|
+
visitERBRescueNode(node) {
|
|
30318
|
+
this.addEmptyBlockOffense(node, node.statements, "rescue");
|
|
30319
|
+
this.visitChildNodes(node);
|
|
30320
|
+
}
|
|
30321
|
+
visitERBEnsureNode(node) {
|
|
30322
|
+
this.addEmptyBlockOffense(node, node.statements, "ensure");
|
|
30323
|
+
this.visitChildNodes(node);
|
|
30324
|
+
}
|
|
30325
|
+
visitERBBlockNode(node) {
|
|
30326
|
+
this.addEmptyBlockOffense(node, node.body, "do");
|
|
30327
|
+
this.visitChildNodes(node);
|
|
30328
|
+
}
|
|
30329
|
+
addEmptyBlockOffense(node, statements, blockType) {
|
|
30330
|
+
this.addEmptyBlockOffenseWithEnd(node, statements, blockType, null);
|
|
30331
|
+
}
|
|
30332
|
+
addEmptyBlockOffenseWithEnd(node, statements, blockType, subsequentNode) {
|
|
30333
|
+
if (this.statementsHaveContent(statements)) {
|
|
30334
|
+
return;
|
|
30335
|
+
}
|
|
30336
|
+
const startLocation = node.location.start;
|
|
30337
|
+
const endLocation = subsequentNode
|
|
30338
|
+
? subsequentNode.location.start
|
|
30339
|
+
: node.location.end;
|
|
30340
|
+
const location = Location.from(startLocation.line, startLocation.column, endLocation.line, endLocation.column);
|
|
30341
|
+
const offense = this.createOffense(`Empty ${blockType} block: this control flow statement has no content`, location);
|
|
30342
|
+
offense.tags = ["unnecessary"];
|
|
30343
|
+
this.offenses.push(offense);
|
|
30344
|
+
}
|
|
30345
|
+
statementsHaveContent(statements) {
|
|
30346
|
+
return statements.some(statement => {
|
|
30347
|
+
if (isHTMLTextNode(statement)) {
|
|
30348
|
+
return statement.content.trim() !== "";
|
|
30349
|
+
}
|
|
30350
|
+
return true;
|
|
30351
|
+
});
|
|
30352
|
+
}
|
|
30353
|
+
markIfChainAsProcessed(node) {
|
|
30354
|
+
this.processedIfNodes.add(node);
|
|
30355
|
+
this.traverseSubsequentNodes(node.subsequent, (current) => {
|
|
30356
|
+
if (isERBIfNode(current)) {
|
|
30357
|
+
this.processedIfNodes.add(current);
|
|
30358
|
+
}
|
|
30359
|
+
});
|
|
30360
|
+
}
|
|
30361
|
+
markElseNodesInIfChain(node) {
|
|
30362
|
+
this.traverseSubsequentNodes(node.subsequent, (current) => {
|
|
30363
|
+
if (isERBElseNode(current)) {
|
|
30364
|
+
this.processedElseNodes.add(current);
|
|
30365
|
+
}
|
|
30366
|
+
});
|
|
30367
|
+
}
|
|
30368
|
+
traverseSubsequentNodes(startNode, callback) {
|
|
30369
|
+
let current = startNode;
|
|
30370
|
+
while (current) {
|
|
30371
|
+
if (isERBIfNode(current)) {
|
|
30372
|
+
callback(current);
|
|
30373
|
+
current = current.subsequent;
|
|
30374
|
+
}
|
|
30375
|
+
else if (isERBElseNode(current)) {
|
|
30376
|
+
callback(current);
|
|
30377
|
+
break;
|
|
30378
|
+
}
|
|
30379
|
+
else {
|
|
30380
|
+
break;
|
|
30381
|
+
}
|
|
30382
|
+
}
|
|
30383
|
+
}
|
|
30384
|
+
checkIfChainParts(node) {
|
|
30385
|
+
if (!this.statementsHaveContent(node.statements)) {
|
|
30386
|
+
this.addEmptyBlockOffenseWithEnd(node, node.statements, "if", node.subsequent);
|
|
30387
|
+
}
|
|
30388
|
+
this.traverseSubsequentNodes(node.subsequent, (current) => {
|
|
30389
|
+
if (this.statementsHaveContent(current.statements)) {
|
|
30390
|
+
return;
|
|
30391
|
+
}
|
|
30392
|
+
const blockType = isERBIfNode(current) ? "elsif" : "else";
|
|
30393
|
+
const nextSubsequent = isERBIfNode(current) ? current.subsequent : null;
|
|
30394
|
+
if (nextSubsequent) {
|
|
30395
|
+
this.addEmptyBlockOffenseWithEnd(current, current.statements, blockType, nextSubsequent);
|
|
30396
|
+
}
|
|
30397
|
+
else {
|
|
30398
|
+
this.addEmptyBlockOffense(current, current.statements, blockType);
|
|
30399
|
+
}
|
|
30400
|
+
});
|
|
30401
|
+
}
|
|
30402
|
+
isEntireIfChainEmpty(node) {
|
|
30403
|
+
if (this.statementsHaveContent(node.statements)) {
|
|
30404
|
+
return false;
|
|
30405
|
+
}
|
|
30406
|
+
let hasContent = false;
|
|
30407
|
+
this.traverseSubsequentNodes(node.subsequent, (current) => {
|
|
30408
|
+
if (this.statementsHaveContent(current.statements)) {
|
|
30409
|
+
hasContent = true;
|
|
30410
|
+
}
|
|
30411
|
+
});
|
|
30412
|
+
return !hasContent;
|
|
30413
|
+
}
|
|
30414
|
+
}
|
|
30415
|
+
class ERBNoEmptyControlFlowRule extends ParserRule {
|
|
30416
|
+
static ruleName = "erb-no-empty-control-flow";
|
|
30417
|
+
get defaultConfig() {
|
|
30418
|
+
return {
|
|
30419
|
+
enabled: true,
|
|
30420
|
+
severity: "hint"
|
|
30421
|
+
};
|
|
30422
|
+
}
|
|
30423
|
+
check(result, context) {
|
|
30424
|
+
const visitor = new ERBNoEmptyControlFlowVisitor(this.ruleName, context);
|
|
30425
|
+
visitor.visit(result.value);
|
|
30426
|
+
return visitor.offenses;
|
|
30427
|
+
}
|
|
30428
|
+
}
|
|
30429
|
+
|
|
29629
30430
|
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; }
|
|
29630
30431
|
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; }
|
|
29631
30432
|
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; }
|
|
@@ -29784,6 +30585,15 @@ class ERBNoConditionalOpenTagRule extends ParserRule {
|
|
|
29784
30585
|
function getSignificantNodes(statements) {
|
|
29785
30586
|
return statements.filter(node => !isPureWhitespaceNode(node));
|
|
29786
30587
|
}
|
|
30588
|
+
function trimWhitespaceNodes(nodes) {
|
|
30589
|
+
let start = 0;
|
|
30590
|
+
let end = nodes.length;
|
|
30591
|
+
while (start < end && isPureWhitespaceNode(nodes[start]))
|
|
30592
|
+
start++;
|
|
30593
|
+
while (end > start && isPureWhitespaceNode(nodes[end - 1]))
|
|
30594
|
+
end--;
|
|
30595
|
+
return nodes.slice(start, end);
|
|
30596
|
+
}
|
|
29787
30597
|
function allEquivalentElements(nodes) {
|
|
29788
30598
|
if (nodes.length < 2)
|
|
29789
30599
|
return false;
|
|
@@ -29901,9 +30711,19 @@ class ERBNoDuplicateBranchElementsVisitor extends BaseRuleVisitor {
|
|
|
29901
30711
|
if (isERBIfNode(node)) {
|
|
29902
30712
|
this.markSubsequentIfNodesAsProcessed(node);
|
|
29903
30713
|
}
|
|
30714
|
+
if (this.allBranchesIdentical(branches)) {
|
|
30715
|
+
this.addOffense("All branches of this conditional have identical content. The conditional can be removed.", node.location, { node: node, allIdentical: true }, "warning");
|
|
30716
|
+
return;
|
|
30717
|
+
}
|
|
29904
30718
|
const state = { isFirstOffense: true };
|
|
29905
30719
|
this.checkBranches(branches, node, state);
|
|
29906
30720
|
}
|
|
30721
|
+
allBranchesIdentical(branches) {
|
|
30722
|
+
if (branches.length < 2)
|
|
30723
|
+
return false;
|
|
30724
|
+
const first = branches[0].map(node => IdentityPrinter.print(node)).join("");
|
|
30725
|
+
return branches.slice(1).every(branch => branch.map(node => IdentityPrinter.print(node)).join("") === first);
|
|
30726
|
+
}
|
|
29907
30727
|
markSubsequentIfNodesAsProcessed(node) {
|
|
29908
30728
|
let current = node.subsequent;
|
|
29909
30729
|
while (current) {
|
|
@@ -29937,11 +30757,23 @@ class ERBNoDuplicateBranchElementsVisitor extends BaseRuleVisitor {
|
|
|
29937
30757
|
const bodiesMatch = elements.every(element => IdentityPrinter.print(element) === IdentityPrinter.print(elements[0]));
|
|
29938
30758
|
for (const element of elements) {
|
|
29939
30759
|
const printed = IdentityPrinter.print(element.open_tag);
|
|
29940
|
-
|
|
29941
|
-
|
|
29942
|
-
|
|
29943
|
-
|
|
29944
|
-
|
|
30760
|
+
if (bodiesMatch) {
|
|
30761
|
+
const autofixContext = state.isFirstOffense
|
|
30762
|
+
? { node: conditionalNode }
|
|
30763
|
+
: undefined;
|
|
30764
|
+
this.addOffense(`The \`${printed}\` element is duplicated across all branches of this conditional and can be moved outside.`, element.location, autofixContext);
|
|
30765
|
+
state.isFirstOffense = false;
|
|
30766
|
+
}
|
|
30767
|
+
else {
|
|
30768
|
+
const autofixContext = state.isFirstOffense
|
|
30769
|
+
? { node: conditionalNode }
|
|
30770
|
+
: undefined;
|
|
30771
|
+
const tagNameLocation = isHTMLOpenTagNode(element.open_tag) && element.open_tag.tag_name?.location
|
|
30772
|
+
? element.open_tag.tag_name.location
|
|
30773
|
+
: element?.open_tag?.location || element.location;
|
|
30774
|
+
this.addOffense(`The \`${printed}\` tag is repeated across all branches with different content. Consider extracting the shared tag outside the conditional.`, tagNameLocation, autofixContext, "hint");
|
|
30775
|
+
state.isFirstOffense = false;
|
|
30776
|
+
}
|
|
29945
30777
|
}
|
|
29946
30778
|
if (!bodiesMatch && bodies.every(body => body.length > 0)) {
|
|
29947
30779
|
this.checkBranches(bodies, conditionalNode, state);
|
|
@@ -29970,6 +30802,15 @@ class ERBNoDuplicateBranchElementsRule extends ParserRule {
|
|
|
29970
30802
|
const branches = collectBranches(conditionalNode);
|
|
29971
30803
|
if (!branches)
|
|
29972
30804
|
return null;
|
|
30805
|
+
if (offense.autofixContext.allIdentical) {
|
|
30806
|
+
const parentInfo = findParentArray(result.value, conditionalNode);
|
|
30807
|
+
if (!parentInfo)
|
|
30808
|
+
return null;
|
|
30809
|
+
const { array: parentArray, index: conditionalIndex } = parentInfo;
|
|
30810
|
+
const firstBranchContent = trimWhitespaceNodes(branches[0]);
|
|
30811
|
+
parentArray.splice(conditionalIndex, 1, ...firstBranchContent);
|
|
30812
|
+
return result;
|
|
30813
|
+
}
|
|
29973
30814
|
const significantBranches = branches.map(getSignificantNodes);
|
|
29974
30815
|
if (significantBranches.some(branch => branch.length === 0))
|
|
29975
30816
|
return null;
|
|
@@ -29983,23 +30824,51 @@ class ERBNoDuplicateBranchElementsRule extends ParserRule {
|
|
|
29983
30824
|
return null;
|
|
29984
30825
|
let { array: parentArray, index: conditionalIndex } = parentInfo;
|
|
29985
30826
|
let hasWrapped = false;
|
|
30827
|
+
let didMutate = false;
|
|
30828
|
+
let failedToHoistPrefix = false;
|
|
30829
|
+
let hoistedBefore = false;
|
|
29986
30830
|
const hoistElement = (elements, position) => {
|
|
30831
|
+
const actualPosition = (position === "before" && failedToHoistPrefix) ? "after" : position;
|
|
29987
30832
|
const bodiesMatch = elements.every(element => IdentityPrinter.print(element) === IdentityPrinter.print(elements[0]));
|
|
29988
30833
|
if (bodiesMatch) {
|
|
30834
|
+
if (actualPosition === "after") {
|
|
30835
|
+
const currentLengths = branches.map(b => getSignificantNodes(b).length);
|
|
30836
|
+
if (currentLengths.some(l => l !== currentLengths[0]))
|
|
30837
|
+
return;
|
|
30838
|
+
}
|
|
30839
|
+
if (actualPosition === "after" && position === "before") {
|
|
30840
|
+
const isAtEnd = branches.every((branch, index) => {
|
|
30841
|
+
const nodes = getSignificantNodes(branch);
|
|
30842
|
+
return nodes.length > 0 && nodes[nodes.length - 1] === elements[index];
|
|
30843
|
+
});
|
|
30844
|
+
if (!isAtEnd)
|
|
30845
|
+
return;
|
|
30846
|
+
}
|
|
29989
30847
|
for (let i = 0; i < branches.length; i++) {
|
|
29990
30848
|
removeNodeFromArray(branches[i], elements[i]);
|
|
29991
30849
|
}
|
|
29992
|
-
if (
|
|
29993
|
-
parentArray.splice(conditionalIndex, 0, elements[0]);
|
|
29994
|
-
conditionalIndex
|
|
30850
|
+
if (actualPosition === "before") {
|
|
30851
|
+
parentArray.splice(conditionalIndex, 0, elements[0], createLiteral("\n"));
|
|
30852
|
+
conditionalIndex += 2;
|
|
30853
|
+
hoistedBefore = true;
|
|
29995
30854
|
}
|
|
29996
30855
|
else {
|
|
29997
|
-
parentArray.splice(conditionalIndex + 1, 0, elements[0]);
|
|
30856
|
+
parentArray.splice(conditionalIndex + 1, 0, createLiteral("\n"), elements[0]);
|
|
29998
30857
|
}
|
|
30858
|
+
didMutate = true;
|
|
29999
30859
|
}
|
|
30000
30860
|
else {
|
|
30001
30861
|
if (hasWrapped)
|
|
30002
30862
|
return;
|
|
30863
|
+
const canWrap = branches.every((branch, index) => {
|
|
30864
|
+
const remaining = getSignificantNodes(branch);
|
|
30865
|
+
return remaining.length === 1 && remaining[0] === elements[index];
|
|
30866
|
+
});
|
|
30867
|
+
if (!canWrap) {
|
|
30868
|
+
if (position === "before")
|
|
30869
|
+
failedToHoistPrefix = true;
|
|
30870
|
+
return;
|
|
30871
|
+
}
|
|
30003
30872
|
for (let i = 0; i < branches.length; i++) {
|
|
30004
30873
|
replaceNodeWithBody(branches[i], elements[i]);
|
|
30005
30874
|
}
|
|
@@ -30008,6 +30877,7 @@ class ERBNoDuplicateBranchElementsRule extends ParserRule {
|
|
|
30008
30877
|
parentArray = wrapper.body;
|
|
30009
30878
|
conditionalIndex = 1;
|
|
30010
30879
|
hasWrapped = true;
|
|
30880
|
+
didMutate = true;
|
|
30011
30881
|
}
|
|
30012
30882
|
};
|
|
30013
30883
|
for (let index = 0; index < prefixCount; index++) {
|
|
@@ -30018,7 +30888,22 @@ class ERBNoDuplicateBranchElementsRule extends ParserRule {
|
|
|
30018
30888
|
const elements = significantBranches.map(branch => branch[branch.length - 1 - offset]);
|
|
30019
30889
|
hoistElement(elements, "after");
|
|
30020
30890
|
}
|
|
30021
|
-
|
|
30891
|
+
if (!hasWrapped && hoistedBefore) {
|
|
30892
|
+
const remaining = branches.map(branch => getSignificantNodes(branch));
|
|
30893
|
+
if (remaining.every(branch => branch.length === 1) && allEquivalentElements(remaining.map(b => b[0]))) {
|
|
30894
|
+
const elements = remaining.map(b => b[0]);
|
|
30895
|
+
const bodiesMatch = elements.every(el => IdentityPrinter.print(el) === IdentityPrinter.print(elements[0]));
|
|
30896
|
+
if (!bodiesMatch && elements.every(el => el.body.length > 0)) {
|
|
30897
|
+
for (let i = 0; i < branches.length; i++) {
|
|
30898
|
+
replaceNodeWithBody(branches[i], elements[i]);
|
|
30899
|
+
}
|
|
30900
|
+
const wrapper = createWrapper(elements[0], [createLiteral("\n"), conditionalNode, createLiteral("\n")]);
|
|
30901
|
+
parentArray[conditionalIndex] = wrapper;
|
|
30902
|
+
didMutate = true;
|
|
30903
|
+
}
|
|
30904
|
+
}
|
|
30905
|
+
}
|
|
30906
|
+
return didMutate ? result : null;
|
|
30022
30907
|
}
|
|
30023
30908
|
}
|
|
30024
30909
|
|
|
@@ -30544,6 +31429,47 @@ class ERBNoRawOutputInAttributeValueRule extends ParserRule {
|
|
|
30544
31429
|
}
|
|
30545
31430
|
}
|
|
30546
31431
|
|
|
31432
|
+
function isAssignmentNode(prismNode) {
|
|
31433
|
+
const type = prismNode?.constructor?.name;
|
|
31434
|
+
if (!type)
|
|
31435
|
+
return false;
|
|
31436
|
+
return type.endsWith("WriteNode");
|
|
31437
|
+
}
|
|
31438
|
+
class ERBNoSilentStatementVisitor extends BaseRuleVisitor {
|
|
31439
|
+
visitERBContentNode(node) {
|
|
31440
|
+
if (isERBOutputNode(node))
|
|
31441
|
+
return;
|
|
31442
|
+
const prismNode = node.prismNode;
|
|
31443
|
+
if (!prismNode)
|
|
31444
|
+
return;
|
|
31445
|
+
if (isAssignmentNode(prismNode))
|
|
31446
|
+
return;
|
|
31447
|
+
const content = node.content?.value?.trim();
|
|
31448
|
+
if (!content)
|
|
31449
|
+
return;
|
|
31450
|
+
this.addOffense(`Avoid using silent ERB tags for statements. Move \`${content}\` to a controller, helper, or presenter.`, node.location);
|
|
31451
|
+
}
|
|
31452
|
+
}
|
|
31453
|
+
class ERBNoSilentStatementRule extends ParserRule {
|
|
31454
|
+
static ruleName = "erb-no-silent-statement";
|
|
31455
|
+
get defaultConfig() {
|
|
31456
|
+
return {
|
|
31457
|
+
enabled: false,
|
|
31458
|
+
severity: "warning"
|
|
31459
|
+
};
|
|
31460
|
+
}
|
|
31461
|
+
get parserOptions() {
|
|
31462
|
+
return {
|
|
31463
|
+
prism_nodes: true,
|
|
31464
|
+
};
|
|
31465
|
+
}
|
|
31466
|
+
check(result, context) {
|
|
31467
|
+
const visitor = new ERBNoSilentStatementVisitor(this.ruleName, context);
|
|
31468
|
+
visitor.visit(result.value);
|
|
31469
|
+
return visitor.offenses;
|
|
31470
|
+
}
|
|
31471
|
+
}
|
|
31472
|
+
|
|
30547
31473
|
class ERBNoSilentTagInAttributeNameVisitor extends BaseRuleVisitor {
|
|
30548
31474
|
visitHTMLAttributeNameNode(node) {
|
|
30549
31475
|
const erbNodes = filterERBContentNodes(node.children);
|
|
@@ -30806,7 +31732,7 @@ class ERBNoTrailingWhitespaceRule extends ParserRule {
|
|
|
30806
31732
|
}
|
|
30807
31733
|
|
|
30808
31734
|
const JS_ATTRIBUTE_PATTERN = /^on/i;
|
|
30809
|
-
const SAFE_PATTERN
|
|
31735
|
+
const SAFE_PATTERN = /\.to_json\s*$|\bj\s*[\s(]|\bescape_javascript\s*[\s(]/;
|
|
30810
31736
|
class ERBNoUnsafeJSAttributeVisitor extends AttributeVisitorMixin {
|
|
30811
31737
|
checkStaticAttributeDynamicValue({ attributeName, valueNodes }) {
|
|
30812
31738
|
if (!JS_ATTRIBUTE_PATTERN.test(attributeName))
|
|
@@ -30817,7 +31743,7 @@ class ERBNoUnsafeJSAttributeVisitor extends AttributeVisitorMixin {
|
|
|
30817
31743
|
if (!isERBOutputNode(node))
|
|
30818
31744
|
continue;
|
|
30819
31745
|
const content = node.content?.value?.trim() || "";
|
|
30820
|
-
if (SAFE_PATTERN
|
|
31746
|
+
if (SAFE_PATTERN.test(content))
|
|
30821
31747
|
continue;
|
|
30822
31748
|
this.addOffense(`Unsafe ERB output in \`${attributeName}\` attribute. Use \`.to_json\`, \`j()\`, or \`escape_javascript()\` to safely encode values.`, node.location);
|
|
30823
31749
|
}
|
|
@@ -30898,7 +31824,27 @@ class ERBNoUnsafeRawRule extends ParserRule {
|
|
|
30898
31824
|
}
|
|
30899
31825
|
}
|
|
30900
31826
|
|
|
30901
|
-
const
|
|
31827
|
+
const SAFE_METHOD_NAMES = new Set([
|
|
31828
|
+
"to_json",
|
|
31829
|
+
"json_escape",
|
|
31830
|
+
]);
|
|
31831
|
+
const ESCAPE_JAVASCRIPT_METHOD_NAMES = new Set([
|
|
31832
|
+
"j",
|
|
31833
|
+
"escape_javascript",
|
|
31834
|
+
]);
|
|
31835
|
+
class SafeCallDetector extends Visitor$1 {
|
|
31836
|
+
hasSafeCall = false;
|
|
31837
|
+
hasEscapeJavascriptCall = false;
|
|
31838
|
+
visitCallNode(node) {
|
|
31839
|
+
if (SAFE_METHOD_NAMES.has(node.name)) {
|
|
31840
|
+
this.hasSafeCall = true;
|
|
31841
|
+
}
|
|
31842
|
+
if (ESCAPE_JAVASCRIPT_METHOD_NAMES.has(node.name)) {
|
|
31843
|
+
this.hasEscapeJavascriptCall = true;
|
|
31844
|
+
}
|
|
31845
|
+
this.visitChildNodes(node);
|
|
31846
|
+
}
|
|
31847
|
+
}
|
|
30902
31848
|
class ERBNoUnsafeScriptInterpolationVisitor extends BaseRuleVisitor {
|
|
30903
31849
|
visitHTMLElementNode(node) {
|
|
30904
31850
|
if (!isHTMLOpenTagNode(node.open_tag)) {
|
|
@@ -30927,9 +31873,17 @@ class ERBNoUnsafeScriptInterpolationVisitor extends BaseRuleVisitor {
|
|
|
30927
31873
|
continue;
|
|
30928
31874
|
if (!isERBOutputNode(child))
|
|
30929
31875
|
continue;
|
|
30930
|
-
const
|
|
30931
|
-
|
|
31876
|
+
const erbContent = child;
|
|
31877
|
+
const prismNode = erbContent.prismNode;
|
|
31878
|
+
const detector = new SafeCallDetector();
|
|
31879
|
+
if (prismNode)
|
|
31880
|
+
detector.visit(prismNode);
|
|
31881
|
+
if (detector.hasSafeCall)
|
|
31882
|
+
continue;
|
|
31883
|
+
if (detector.hasEscapeJavascriptCall) {
|
|
31884
|
+
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);
|
|
30932
31885
|
continue;
|
|
31886
|
+
}
|
|
30933
31887
|
this.addOffense("Unsafe ERB output in `<script>` tag. Use `.to_json` to safely serialize values into JavaScript.", child.location);
|
|
30934
31888
|
}
|
|
30935
31889
|
}
|
|
@@ -30942,6 +31896,11 @@ class ERBNoUnsafeScriptInterpolationRule extends ParserRule {
|
|
|
30942
31896
|
severity: "error"
|
|
30943
31897
|
};
|
|
30944
31898
|
}
|
|
31899
|
+
get parserOptions() {
|
|
31900
|
+
return {
|
|
31901
|
+
prism_nodes: true,
|
|
31902
|
+
};
|
|
31903
|
+
}
|
|
30945
31904
|
check(result, context) {
|
|
30946
31905
|
const visitor = new ERBNoUnsafeScriptInterpolationVisitor(this.ruleName, context);
|
|
30947
31906
|
visitor.visit(result.value);
|
|
@@ -31949,7 +32908,7 @@ class HerbDisableCommentValidRuleNameRule extends ParserRule {
|
|
|
31949
32908
|
}
|
|
31950
32909
|
}
|
|
31951
32910
|
|
|
31952
|
-
const ALLOWED_TYPES = ["text/javascript"];
|
|
32911
|
+
const ALLOWED_TYPES = ["text/javascript", "module", "importmap", "speculationrules"];
|
|
31953
32912
|
class AllowedScriptTypeVisitor extends BaseRuleVisitor {
|
|
31954
32913
|
visitHTMLOpenTagNode(node) {
|
|
31955
32914
|
if (getTagLocalName(node) === "script") {
|
|
@@ -32487,6 +33446,47 @@ class HTMLBodyOnlyElementsRule extends ParserRule {
|
|
|
32487
33446
|
}
|
|
32488
33447
|
}
|
|
32489
33448
|
|
|
33449
|
+
class BooleanAttributesNoValueVisitor extends AttributeVisitorMixin {
|
|
33450
|
+
checkStaticAttributeStaticValue({ originalAttributeName, attributeNode }) {
|
|
33451
|
+
this.checkAttribute(originalAttributeName, attributeNode);
|
|
33452
|
+
}
|
|
33453
|
+
checkStaticAttributeDynamicValue({ originalAttributeName, attributeNode }) {
|
|
33454
|
+
this.checkAttribute(originalAttributeName, attributeNode);
|
|
33455
|
+
}
|
|
33456
|
+
checkAttribute(attributeName, attributeNode) {
|
|
33457
|
+
if (!isBooleanAttribute(attributeName))
|
|
33458
|
+
return;
|
|
33459
|
+
if (!hasAttributeValue(attributeNode))
|
|
33460
|
+
return;
|
|
33461
|
+
this.addOffense(`Boolean attribute \`${IdentityPrinter.print(attributeNode.name)}\` should not have a value. Use \`${attributeName.toLowerCase()}\` instead of \`${IdentityPrinter.print(attributeNode)}\`.`, attributeNode.value.location, {
|
|
33462
|
+
node: attributeNode
|
|
33463
|
+
});
|
|
33464
|
+
}
|
|
33465
|
+
}
|
|
33466
|
+
class HTMLBooleanAttributesNoValueRule extends ParserRule {
|
|
33467
|
+
static autocorrectable = true;
|
|
33468
|
+
static ruleName = "html-boolean-attributes-no-value";
|
|
33469
|
+
get defaultConfig() {
|
|
33470
|
+
return {
|
|
33471
|
+
enabled: true,
|
|
33472
|
+
severity: "error"
|
|
33473
|
+
};
|
|
33474
|
+
}
|
|
33475
|
+
check(result, context) {
|
|
33476
|
+
const visitor = new BooleanAttributesNoValueVisitor(this.ruleName, context);
|
|
33477
|
+
visitor.visit(result.value);
|
|
33478
|
+
return visitor.offenses;
|
|
33479
|
+
}
|
|
33480
|
+
autofix(offense, result, _context) {
|
|
33481
|
+
if (!offense.autofixContext)
|
|
33482
|
+
return null;
|
|
33483
|
+
const { node } = offense.autofixContext;
|
|
33484
|
+
node.equals = null;
|
|
33485
|
+
node.value = null;
|
|
33486
|
+
return result;
|
|
33487
|
+
}
|
|
33488
|
+
}
|
|
33489
|
+
|
|
32490
33490
|
class DetailsHasSummaryVisitor extends BaseRuleVisitor {
|
|
32491
33491
|
visitHTMLElementNode(node) {
|
|
32492
33492
|
this.checkDetailsElement(node);
|
|
@@ -32536,47 +33536,6 @@ class HTMLDetailsHasSummaryRule extends ParserRule {
|
|
|
32536
33536
|
}
|
|
32537
33537
|
}
|
|
32538
33538
|
|
|
32539
|
-
class BooleanAttributesNoValueVisitor extends AttributeVisitorMixin {
|
|
32540
|
-
checkStaticAttributeStaticValue({ originalAttributeName, attributeNode }) {
|
|
32541
|
-
this.checkAttribute(originalAttributeName, attributeNode);
|
|
32542
|
-
}
|
|
32543
|
-
checkStaticAttributeDynamicValue({ originalAttributeName, attributeNode }) {
|
|
32544
|
-
this.checkAttribute(originalAttributeName, attributeNode);
|
|
32545
|
-
}
|
|
32546
|
-
checkAttribute(attributeName, attributeNode) {
|
|
32547
|
-
if (!isBooleanAttribute(attributeName))
|
|
32548
|
-
return;
|
|
32549
|
-
if (!hasAttributeValue(attributeNode))
|
|
32550
|
-
return;
|
|
32551
|
-
this.addOffense(`Boolean attribute \`${IdentityPrinter.print(attributeNode.name)}\` should not have a value. Use \`${attributeName.toLowerCase()}\` instead of \`${IdentityPrinter.print(attributeNode)}\`.`, attributeNode.value.location, {
|
|
32552
|
-
node: attributeNode
|
|
32553
|
-
});
|
|
32554
|
-
}
|
|
32555
|
-
}
|
|
32556
|
-
class HTMLBooleanAttributesNoValueRule extends ParserRule {
|
|
32557
|
-
static autocorrectable = true;
|
|
32558
|
-
static ruleName = "html-boolean-attributes-no-value";
|
|
32559
|
-
get defaultConfig() {
|
|
32560
|
-
return {
|
|
32561
|
-
enabled: true,
|
|
32562
|
-
severity: "error"
|
|
32563
|
-
};
|
|
32564
|
-
}
|
|
32565
|
-
check(result, context) {
|
|
32566
|
-
const visitor = new BooleanAttributesNoValueVisitor(this.ruleName, context);
|
|
32567
|
-
visitor.visit(result.value);
|
|
32568
|
-
return visitor.offenses;
|
|
32569
|
-
}
|
|
32570
|
-
autofix(offense, result, _context) {
|
|
32571
|
-
if (!offense.autofixContext)
|
|
32572
|
-
return null;
|
|
32573
|
-
const { node } = offense.autofixContext;
|
|
32574
|
-
node.equals = null;
|
|
32575
|
-
node.value = null;
|
|
32576
|
-
return result;
|
|
32577
|
-
}
|
|
32578
|
-
}
|
|
32579
|
-
|
|
32580
33539
|
class HeadOnlyElementsVisitor extends BaseRuleVisitor {
|
|
32581
33540
|
elementStack = [];
|
|
32582
33541
|
visitHTMLElementNode(node) {
|
|
@@ -34259,8 +35218,10 @@ class TurboPermanentRequireIdRule extends ParserRule {
|
|
|
34259
35218
|
|
|
34260
35219
|
const rules = [
|
|
34261
35220
|
ActionViewNoSilentHelperRule,
|
|
35221
|
+
ActionViewNoSilentRenderRule,
|
|
34262
35222
|
ERBCommentSyntax,
|
|
34263
35223
|
ERBNoCaseNodeChildrenRule,
|
|
35224
|
+
ERBNoEmptyControlFlowRule,
|
|
34264
35225
|
ERBNoConditionalHTMLElementRule,
|
|
34265
35226
|
ERBNoConditionalOpenTagRule,
|
|
34266
35227
|
ERBNoDuplicateBranchElementsRule,
|
|
@@ -34275,6 +35236,7 @@ const rules = [
|
|
|
34275
35236
|
ERBNoOutputInAttributeNameRule,
|
|
34276
35237
|
ERBNoOutputInAttributePositionRule,
|
|
34277
35238
|
ERBNoRawOutputInAttributeValueRule,
|
|
35239
|
+
ERBNoSilentStatementRule,
|
|
34278
35240
|
ERBNoSilentTagInAttributeNameRule,
|
|
34279
35241
|
ERBNoStatementInScriptRule,
|
|
34280
35242
|
ERBNoThenInControlFlowRule,
|
|
@@ -34306,8 +35268,8 @@ const rules = [
|
|
|
34306
35268
|
HTMLAttributeValuesRequireQuotesRule,
|
|
34307
35269
|
HTMLAvoidBothDisabledAndAriaDisabledRule,
|
|
34308
35270
|
HTMLBodyOnlyElementsRule,
|
|
34309
|
-
HTMLDetailsHasSummaryRule,
|
|
34310
35271
|
HTMLBooleanAttributesNoValueRule,
|
|
35272
|
+
HTMLDetailsHasSummaryRule,
|
|
34311
35273
|
HTMLHeadOnlyElementsRule,
|
|
34312
35274
|
HTMLIframeHasTitleRule,
|
|
34313
35275
|
HTMLImgRequireAltRule,
|
|
@@ -35882,5 +36844,5 @@ async function loadCustomRules(options) {
|
|
|
35882
36844
|
};
|
|
35883
36845
|
}
|
|
35884
36846
|
|
|
35885
|
-
export { ABSTRACT_ARIA_ROLES, ARIA_ATTRIBUTES, AttributeVisitorMixin, BaseLexerRuleVisitor, BaseRuleVisitor, BaseSourceRuleVisitor, ControlFlowTrackingVisitor, ControlFlowType, CustomRuleLoader, DEFAULT_LINTER_PARSER_OPTIONS, DEFAULT_LINT_CONTEXT, DEFAULT_RULE_CONFIG, DOCUMENT_ONLY_TAG_NAMES, ERBCommentSyntax, ERBNoCaseNodeChildrenRule, ERBNoConditionalOpenTagRule, ERBNoDuplicateBranchElementsRule, ERBNoEmptyTagsRule, ERBNoExtraNewLineRule, ERBNoExtraWhitespaceRule, ERBNoInlineCaseConditionsRule, ERBNoInstanceVariablesInPartialsRule, ERBNoJavascriptTagHelperRule, ERBNoOutputControlFlowRule, ERBNoOutputInAttributeNameRule, ERBNoOutputInAttributePositionRule, ERBNoRawOutputInAttributeValueRule, ERBNoSilentTagInAttributeNameRule, ERBNoStatementInScriptRule, ERBNoThenInControlFlowRule, ERBNoTrailingWhitespaceRule, ERBNoUnsafeJSAttributeRule, ERBNoUnsafeRawRule, ERBNoUnsafeScriptInterpolationRule, ERBPreferImageTagHelperRule, ERBRequireTrailingNewlineRule, ERBRequireWhitespaceRule, ERBRightTrimRule, ERBStrictLocalsCommentSyntaxRule, ERBStrictLocalsRequiredRule, HEADING_TAGS, HEAD_AND_BODY_TAG_NAMES, HEAD_ONLY_TAG_NAMES, HTMLAllowedScriptTypeRule, HTMLAnchorRequireHrefRule, HTMLAriaLabelIsWellFormattedRule, HTMLAriaLevelMustBeValidRule, HTMLAriaRoleHeadingRequiresLevelRule, HTMLAriaRoleMustBeValidRule, HTMLAttributeDoubleQuotesRule, HTMLAttributeEqualsSpacingRule, HTMLAttributeValuesRequireQuotesRule, HTMLAvoidBothDisabledAndAriaDisabledRule, HTMLBodyOnlyElementsRule, HTMLBooleanAttributesNoValueRule, HTMLDetailsHasSummaryRule, HTMLHeadOnlyElementsRule, HTMLIframeHasTitleRule, HTMLImgRequireAltRule, HTMLInputRequireAutocompleteRule, HTMLNavigationHasLabelRule, HTMLNoAbstractRolesRule, HTMLNoAriaHiddenOnBodyRule, HTMLNoAriaHiddenOnFocusableRule, HTMLNoBlockInsideInlineRule, HTMLNoDuplicateAttributesRule, HTMLNoDuplicateIdsRule, HTMLNoDuplicateMetaNamesRule, HTMLNoEmptyAttributesRule, HTMLNoEmptyHeadingsRule, HTMLNoNestedLinksRule, HTMLNoPositiveTabIndexRule, HTMLNoSelfClosingRule, HTMLNoSpaceInTagRule, HTMLNoTitleAttributeRule, HTMLNoUnderscoresInAttributeNamesRule, HTMLRequireClosingTagsRule, HTMLTagNameLowercaseRule, HTML_BLOCK_ELEMENTS, HTML_BOOLEAN_ATTRIBUTES, HTML_INLINE_ELEMENTS, HTML_ONLY_TAG_NAMES, HTML_VOID_ELEMENTS, HerbDisableCommentBaseVisitor, HerbDisableCommentMalformedRule, HerbDisableCommentMissingRulesRule, HerbDisableCommentNoDuplicateRulesRule, HerbDisableCommentNoRedundantAllRule, HerbDisableCommentParsedVisitor, HerbDisableCommentUnnecessaryRule, HerbDisableCommentValidRuleNameRule, LexerRule, Linter, ParserRule, STRICT_LOCALS_PATTERN, SVGTagNameCapitalizationRule, SVG_CAMEL_CASE_ELEMENTS, SVG_LOWERCASE_TO_CAMELCASE, SourceRule, VALID_ARIA_ROLES, createEndOfFileLocation, findNodeAtPosition, findNodeByLocation, findParent, getBasename, hasBalancedParentheses, isBlockElement, isBodyOnlyTag, isBodyTag, isBooleanAttribute, isDocumentOnlyTag, isHeadAndBodyTag, isHeadOnlyTag, isHeadTag, isHtmlOnlyTag, isInlineElement, isPartialFile, isVoidElement, loadCustomRules, locationFromOffset, locationsEqual, positionFromOffset, ruleDocumentationUrl, rules, splitByTopLevelComma };
|
|
36847
|
+
export { ABSTRACT_ARIA_ROLES, ARIA_ATTRIBUTES, ActionViewNoSilentHelperRule, ActionViewNoSilentRenderRule, AttributeVisitorMixin, BaseLexerRuleVisitor, BaseRuleVisitor, BaseSourceRuleVisitor, ControlFlowTrackingVisitor, ControlFlowType, CustomRuleLoader, DEFAULT_LINTER_PARSER_OPTIONS, DEFAULT_LINT_CONTEXT, DEFAULT_RULE_CONFIG, DOCUMENT_ONLY_TAG_NAMES, ERBCommentSyntax, ERBNoCaseNodeChildrenRule, ERBNoConditionalOpenTagRule, ERBNoDuplicateBranchElementsRule, ERBNoEmptyControlFlowRule, ERBNoEmptyTagsRule, ERBNoExtraNewLineRule, ERBNoExtraWhitespaceRule, ERBNoInlineCaseConditionsRule, ERBNoInstanceVariablesInPartialsRule, ERBNoJavascriptTagHelperRule, ERBNoOutputControlFlowRule, ERBNoOutputInAttributeNameRule, ERBNoOutputInAttributePositionRule, ERBNoRawOutputInAttributeValueRule, ERBNoSilentStatementRule, ERBNoSilentTagInAttributeNameRule, ERBNoStatementInScriptRule, ERBNoThenInControlFlowRule, ERBNoTrailingWhitespaceRule, ERBNoUnsafeJSAttributeRule, ERBNoUnsafeRawRule, ERBNoUnsafeScriptInterpolationRule, ERBPreferImageTagHelperRule, ERBRequireTrailingNewlineRule, ERBRequireWhitespaceRule, ERBRightTrimRule, ERBStrictLocalsCommentSyntaxRule, ERBStrictLocalsRequiredRule, HEADING_TAGS, HEAD_AND_BODY_TAG_NAMES, HEAD_ONLY_TAG_NAMES, HTMLAllowedScriptTypeRule, HTMLAnchorRequireHrefRule, HTMLAriaLabelIsWellFormattedRule, HTMLAriaLevelMustBeValidRule, HTMLAriaRoleHeadingRequiresLevelRule, HTMLAriaRoleMustBeValidRule, HTMLAttributeDoubleQuotesRule, HTMLAttributeEqualsSpacingRule, HTMLAttributeValuesRequireQuotesRule, HTMLAvoidBothDisabledAndAriaDisabledRule, HTMLBodyOnlyElementsRule, HTMLBooleanAttributesNoValueRule, HTMLDetailsHasSummaryRule, HTMLHeadOnlyElementsRule, HTMLIframeHasTitleRule, HTMLImgRequireAltRule, HTMLInputRequireAutocompleteRule, HTMLNavigationHasLabelRule, HTMLNoAbstractRolesRule, HTMLNoAriaHiddenOnBodyRule, HTMLNoAriaHiddenOnFocusableRule, HTMLNoBlockInsideInlineRule, HTMLNoDuplicateAttributesRule, HTMLNoDuplicateIdsRule, HTMLNoDuplicateMetaNamesRule, HTMLNoEmptyAttributesRule, HTMLNoEmptyHeadingsRule, HTMLNoNestedLinksRule, HTMLNoPositiveTabIndexRule, HTMLNoSelfClosingRule, HTMLNoSpaceInTagRule, HTMLNoTitleAttributeRule, HTMLNoUnderscoresInAttributeNamesRule, HTMLRequireClosingTagsRule, HTMLTagNameLowercaseRule, HTML_BLOCK_ELEMENTS, HTML_BOOLEAN_ATTRIBUTES, HTML_INLINE_ELEMENTS, HTML_ONLY_TAG_NAMES, HTML_VOID_ELEMENTS, HerbDisableCommentBaseVisitor, HerbDisableCommentMalformedRule, HerbDisableCommentMissingRulesRule, HerbDisableCommentNoDuplicateRulesRule, HerbDisableCommentNoRedundantAllRule, HerbDisableCommentParsedVisitor, HerbDisableCommentUnnecessaryRule, HerbDisableCommentValidRuleNameRule, LexerRule, Linter, ParserRule, STRICT_LOCALS_PATTERN, SVGTagNameCapitalizationRule, SVG_CAMEL_CASE_ELEMENTS, SVG_LOWERCASE_TO_CAMELCASE, SourceRule, VALID_ARIA_ROLES, createEndOfFileLocation, findAttributeByName, findNodeAtPosition, findNodeByLocation, findParent, getAttribute, getAttributeName, getAttributeValue, getAttributeValueNodes, getAttributeValueQuoteType, getAttributes, getBasename, getCombinedAttributeNameString, getStaticAttributeValue, getStaticAttributeValueContent, getTagName, hasAttribute, hasAttributeValue, hasBalancedParentheses, hasDynamicAttributeName, hasDynamicAttributeValue, hasStaticAttributeValue, hasStaticAttributeValueContent, isAttributeValueQuoted, isBlockElement, isBodyOnlyTag, isBodyTag, isBooleanAttribute, isDocumentOnlyTag, isHeadAndBodyTag, isHeadOnlyTag, isHeadTag, isHtmlOnlyTag, isInlineElement, isPartialFile, isVoidElement, loadCustomRules, locationFromOffset, locationsEqual, positionFromOffset, ruleDocumentationUrl, rules, splitByTopLevelComma };
|
|
35886
36848
|
//# sourceMappingURL=loader.js.map
|