Skip to content

Commit 49ffde6

Browse files
authored
Support custom modifiers via BlockSchema & LabelSchema (#106)
* use fully qualified path to stringer * Remove deprecated token modifier This aligns with conclusions from microsoft/language-server-protocol#1415 * turn token modifiers from uint to string This enables downstream schemas to declare their own modifiers, e.g. "terraform-resource". * support custom modifiers via BlockSchema & LabelSchema * lang: turn uint sem tokens into string This makes it possible to use the types directly downstream without having to convert them into strings.
1 parent cd671c7 commit 49ffde6

9 files changed

+361
-121
lines changed

decoder/semantic_tokens.go

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func (d *PathDecoder) SemanticTokensInFile(filename string) ([]lang.SemanticToke
2828
return []lang.SemanticToken{}, nil
2929
}
3030

31-
tokens := d.tokensForBody(body, d.pathCtx.Schema, false)
31+
tokens := d.tokensForBody(body, d.pathCtx.Schema, false, []lang.SemanticTokenModifier{})
3232

3333
sort.Slice(tokens, func(i, j int) bool {
3434
return tokens[i].Range.Start.Byte < tokens[j].Range.Start.Byte
@@ -37,7 +37,9 @@ func (d *PathDecoder) SemanticTokensInFile(filename string) ([]lang.SemanticToke
3737
return tokens, nil
3838
}
3939

40-
func (d *PathDecoder) tokensForBody(body *hclsyntax.Body, bodySchema *schema.BodySchema, isDependent bool) []lang.SemanticToken {
40+
func (d *PathDecoder) tokensForBody(body *hclsyntax.Body, bodySchema *schema.BodySchema,
41+
isDependent bool, parentModifiers []lang.SemanticTokenModifier) []lang.SemanticToken {
42+
4143
tokens := make([]lang.SemanticToken, 0)
4244

4345
if bodySchema == nil {
@@ -58,9 +60,6 @@ func (d *PathDecoder) tokensForBody(body *hclsyntax.Body, bodySchema *schema.Bod
5860
if isDependent {
5961
modifiers = append(modifiers, lang.TokenModifierDependent)
6062
}
61-
if attrSchema.IsDeprecated {
62-
modifiers = append(modifiers, lang.TokenModifierDeprecated)
63-
}
6463

6564
tokens = append(tokens, lang.SemanticToken{
6665
Type: lang.TokenAttrName,
@@ -79,17 +78,19 @@ func (d *PathDecoder) tokensForBody(body *hclsyntax.Body, bodySchema *schema.Bod
7978
continue
8079
}
8180

82-
modifiers := make([]lang.SemanticTokenModifier, 0)
81+
blockModifiers := make([]lang.SemanticTokenModifier, 0)
82+
blockModifiers = append(blockModifiers, parentModifiers...)
83+
8384
if isDependent {
84-
modifiers = append(modifiers, lang.TokenModifierDependent)
85+
blockModifiers = append(blockModifiers, lang.TokenModifierDependent)
8586
}
86-
if blockSchema.IsDeprecated {
87-
modifiers = append(modifiers, lang.TokenModifierDeprecated)
87+
if blockSchema.SemanticTokenModifier != "" {
88+
blockModifiers = append(blockModifiers, blockSchema.SemanticTokenModifier)
8889
}
8990

9091
tokens = append(tokens, lang.SemanticToken{
9192
Type: lang.TokenBlockType,
92-
Modifiers: modifiers,
93+
Modifiers: blockModifiers,
9394
Range: block.TypeRange,
9495
})
9596

@@ -101,25 +102,32 @@ func (d *PathDecoder) tokensForBody(body *hclsyntax.Body, bodySchema *schema.Bod
101102

102103
labelSchema := blockSchema.Labels[i]
103104

104-
modifiers := make([]lang.SemanticTokenModifier, 0)
105+
labelModifiers := make([]lang.SemanticTokenModifier, 0)
106+
labelModifiers = append(labelModifiers, parentModifiers...)
105107
if labelSchema.IsDepKey {
106-
modifiers = append(modifiers, lang.TokenModifierDependent)
108+
labelModifiers = append(labelModifiers, lang.TokenModifierDependent)
109+
}
110+
if blockSchema.SemanticTokenModifier != "" {
111+
labelModifiers = append(labelModifiers, blockSchema.SemanticTokenModifier)
112+
}
113+
if labelSchema.SemanticTokenModifier != "" {
114+
labelModifiers = append(labelModifiers, labelSchema.SemanticTokenModifier)
107115
}
108116

109117
tokens = append(tokens, lang.SemanticToken{
110118
Type: lang.TokenBlockLabel,
111-
Modifiers: modifiers,
119+
Modifiers: labelModifiers,
112120
Range: labelRange,
113121
})
114122
}
115123

116124
if block.Body != nil {
117-
tokens = append(tokens, d.tokensForBody(block.Body, blockSchema.Body, false)...)
125+
tokens = append(tokens, d.tokensForBody(block.Body, blockSchema.Body, false, blockModifiers)...)
118126
}
119127

120128
depSchema, _, ok := NewBlockSchema(blockSchema).DependentBodySchema(block.AsHCLBlock())
121129
if ok {
122-
tokens = append(tokens, d.tokensForBody(block.Body, depSchema, true)...)
130+
tokens = append(tokens, d.tokensForBody(block.Body, depSchema, true, blockModifiers)...)
123131
}
124132
}
125133

decoder/semantic_tokens_test.go

Lines changed: 284 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,8 @@ resource "vault_auth_backend" "blah" {
164164
},
165165
},
166166
{ // source
167-
Type: lang.TokenAttrName,
168-
Modifiers: []lang.SemanticTokenModifier{
169-
lang.TokenModifierDeprecated,
170-
},
167+
Type: lang.TokenAttrName,
168+
Modifiers: []lang.SemanticTokenModifier{},
171169
Range: hcl.Range{
172170
Filename: "test.tf",
173171
Start: hcl.Pos{
@@ -537,3 +535,285 @@ resource "aws_instance" "beta" {
537535
t.Fatalf("unexpected tokens: %s", diff)
538536
}
539537
}
538+
539+
func TestDecoder_SemanticTokensInFile_customModifiers(t *testing.T) {
540+
bodySchema := &schema.BodySchema{
541+
Blocks: map[string]*schema.BlockSchema{
542+
"module": {
543+
SemanticTokenModifier: lang.SemanticTokenModifier("module"),
544+
Labels: []*schema.LabelSchema{
545+
{
546+
Name: "name",
547+
SemanticTokenModifier: lang.SemanticTokenModifier("name"),
548+
},
549+
},
550+
Body: &schema.BodySchema{
551+
Attributes: map[string]*schema.AttributeSchema{
552+
"count": {
553+
Expr: schema.LiteralTypeOnly(cty.Number),
554+
},
555+
"source": {
556+
Expr: schema.LiteralTypeOnly(cty.String),
557+
IsDeprecated: true,
558+
},
559+
},
560+
},
561+
},
562+
"resource": {
563+
SemanticTokenModifier: lang.SemanticTokenModifier("resource"),
564+
Labels: []*schema.LabelSchema{
565+
{
566+
Name: "type",
567+
IsDepKey: true,
568+
SemanticTokenModifier: lang.SemanticTokenModifier("type"),
569+
},
570+
{
571+
Name: "name",
572+
SemanticTokenModifier: lang.SemanticTokenModifier("name"),
573+
},
574+
},
575+
Body: &schema.BodySchema{
576+
Blocks: map[string]*schema.BlockSchema{
577+
"provisioner": {
578+
SemanticTokenModifier: lang.SemanticTokenModifier("provisioner"),
579+
Labels: []*schema.LabelSchema{
580+
{
581+
Name: "type",
582+
SemanticTokenModifier: lang.SemanticTokenModifier("type"),
583+
},
584+
},
585+
},
586+
},
587+
},
588+
},
589+
},
590+
}
591+
592+
testCfg := []byte(`module "ref" {
593+
source = "./sub"
594+
count = 1
595+
}
596+
resource "vault_auth_backend" "blah" {
597+
provisioner "inner" {
598+
test = 42
599+
}
600+
}
601+
`)
602+
603+
f, pDiags := hclsyntax.ParseConfig(testCfg, "test.tf", hcl.InitialPos)
604+
if len(pDiags) > 0 {
605+
t.Fatal(pDiags)
606+
}
607+
608+
d := testPathDecoder(t, &PathContext{
609+
Schema: bodySchema,
610+
Files: map[string]*hcl.File{
611+
"test.tf": f,
612+
},
613+
})
614+
615+
tokens, err := d.SemanticTokensInFile("test.tf")
616+
if err != nil {
617+
t.Fatal(err)
618+
}
619+
620+
expectedTokens := []lang.SemanticToken{
621+
{ // module
622+
Type: lang.TokenBlockType,
623+
Modifiers: []lang.SemanticTokenModifier{
624+
lang.SemanticTokenModifier("module"),
625+
},
626+
Range: hcl.Range{
627+
Filename: "test.tf",
628+
Start: hcl.Pos{
629+
Line: 1,
630+
Column: 1,
631+
Byte: 0,
632+
},
633+
End: hcl.Pos{
634+
Line: 1,
635+
Column: 7,
636+
Byte: 6,
637+
},
638+
},
639+
},
640+
{ // "ref"
641+
Type: lang.TokenBlockLabel,
642+
Modifiers: []lang.SemanticTokenModifier{
643+
lang.SemanticTokenModifier("module"),
644+
lang.SemanticTokenModifier("name"),
645+
},
646+
Range: hcl.Range{
647+
Filename: "test.tf",
648+
Start: hcl.Pos{
649+
Line: 1,
650+
Column: 8,
651+
Byte: 7,
652+
},
653+
End: hcl.Pos{
654+
Line: 1,
655+
Column: 13,
656+
Byte: 12,
657+
},
658+
},
659+
},
660+
{ // source
661+
Type: lang.TokenAttrName,
662+
Modifiers: []lang.SemanticTokenModifier{},
663+
Range: hcl.Range{
664+
Filename: "test.tf",
665+
Start: hcl.Pos{
666+
Line: 2,
667+
Column: 3,
668+
Byte: 17,
669+
},
670+
End: hcl.Pos{
671+
Line: 2,
672+
Column: 9,
673+
Byte: 23,
674+
},
675+
},
676+
},
677+
{ // "./sub"
678+
Type: lang.TokenString,
679+
Modifiers: []lang.SemanticTokenModifier{},
680+
Range: hcl.Range{
681+
Filename: "test.tf",
682+
Start: hcl.Pos{
683+
Line: 2,
684+
Column: 12,
685+
Byte: 26,
686+
},
687+
End: hcl.Pos{
688+
Line: 2,
689+
Column: 19,
690+
Byte: 33,
691+
},
692+
},
693+
},
694+
{ // count
695+
Type: lang.TokenAttrName,
696+
Modifiers: []lang.SemanticTokenModifier{},
697+
Range: hcl.Range{
698+
Filename: "test.tf",
699+
Start: hcl.Pos{
700+
Line: 3,
701+
Column: 3,
702+
Byte: 36,
703+
},
704+
End: hcl.Pos{
705+
Line: 3,
706+
Column: 8,
707+
Byte: 41,
708+
},
709+
},
710+
},
711+
{ // 1
712+
Type: lang.TokenNumber,
713+
Modifiers: []lang.SemanticTokenModifier{},
714+
Range: hcl.Range{
715+
Filename: "test.tf",
716+
Start: hcl.Pos{
717+
Line: 3,
718+
Column: 12,
719+
Byte: 45,
720+
},
721+
End: hcl.Pos{
722+
Line: 3,
723+
Column: 13,
724+
Byte: 46,
725+
},
726+
},
727+
},
728+
{ // resource
729+
Type: lang.TokenBlockType,
730+
Modifiers: []lang.SemanticTokenModifier{
731+
lang.SemanticTokenModifier("resource"),
732+
},
733+
Range: hcl.Range{
734+
Filename: "test.tf",
735+
Start: hcl.Pos{
736+
Line: 5,
737+
Column: 1,
738+
Byte: 49,
739+
},
740+
End: hcl.Pos{
741+
Line: 5,
742+
Column: 9,
743+
Byte: 57,
744+
},
745+
},
746+
},
747+
{ // vault_auth_backend
748+
Type: lang.TokenBlockLabel,
749+
Modifiers: []lang.SemanticTokenModifier{
750+
lang.TokenModifierDependent,
751+
lang.SemanticTokenModifier("resource"),
752+
lang.SemanticTokenModifier("type"),
753+
},
754+
Range: hcl.Range{
755+
Filename: "test.tf",
756+
Start: hcl.Pos{
757+
Line: 5,
758+
Column: 10,
759+
Byte: 58,
760+
},
761+
End: hcl.Pos{
762+
Line: 5,
763+
Column: 30,
764+
Byte: 78,
765+
},
766+
},
767+
},
768+
{ // blah
769+
Type: lang.TokenBlockLabel,
770+
Modifiers: []lang.SemanticTokenModifier{
771+
lang.SemanticTokenModifier("resource"),
772+
lang.SemanticTokenModifier("name"),
773+
},
774+
Range: hcl.Range{
775+
Filename: "test.tf",
776+
Start: hcl.Pos{
777+
Line: 5,
778+
Column: 31,
779+
Byte: 79,
780+
},
781+
End: hcl.Pos{
782+
Line: 5,
783+
Column: 37,
784+
Byte: 85,
785+
},
786+
},
787+
},
788+
{ // provisioner
789+
Type: lang.TokenBlockType,
790+
Modifiers: []lang.SemanticTokenModifier{
791+
lang.SemanticTokenModifier("resource"),
792+
lang.SemanticTokenModifier("provisioner"),
793+
},
794+
Range: hcl.Range{
795+
Filename: "test.tf",
796+
Start: hcl.Pos{Line: 6, Column: 3, Byte: 90},
797+
End: hcl.Pos{Line: 6, Column: 14, Byte: 101},
798+
},
799+
},
800+
{ // "inner"
801+
Type: lang.TokenBlockLabel,
802+
Modifiers: []lang.SemanticTokenModifier{
803+
lang.SemanticTokenModifier("resource"),
804+
lang.SemanticTokenModifier("provisioner"),
805+
lang.SemanticTokenModifier("type"),
806+
},
807+
Range: hcl.Range{
808+
Filename: "test.tf",
809+
Start: hcl.Pos{Line: 6, Column: 15, Byte: 102},
810+
End: hcl.Pos{Line: 6, Column: 22, Byte: 109},
811+
},
812+
},
813+
}
814+
815+
diff := cmp.Diff(expectedTokens, tokens)
816+
if diff != "" {
817+
t.Fatalf("unexpected tokens: %s", diff)
818+
}
819+
}

0 commit comments

Comments
 (0)