-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add snippet validator, fixes #101
- Loading branch information
Showing
5 changed files
with
322 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
package extension | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path" | ||
"path/filepath" | ||
"reflect" | ||
"strings" | ||
|
||
"github.com/wI2L/jsondiff" | ||
) | ||
|
||
func validateStorefrontSnippets(context *ValidationContext) { | ||
rootDir := context.Extension.GetRootDir() | ||
if err := validateStorefrontSnippetsByPath(rootDir, context); err != nil { | ||
return | ||
} | ||
|
||
for _, extraBundle := range context.Extension.GetExtensionConfig().Build.ExtraBundles { | ||
bundlePath := rootDir | ||
|
||
if extraBundle.Path != "" { | ||
bundlePath = path.Join(bundlePath, extraBundle.Path) | ||
} else { | ||
bundlePath = path.Join(bundlePath, extraBundle.Name) | ||
} | ||
|
||
if err := validateStorefrontSnippetsByPath(bundlePath, context); err != nil { | ||
return | ||
} | ||
} | ||
} | ||
|
||
func validateStorefrontSnippetsByPath(extensionRoot string, context *ValidationContext) error { | ||
snippetFolder := path.Join(extensionRoot, "Resources", "snippet") | ||
|
||
if _, err := os.Stat(snippetFolder); err != nil { | ||
return nil //nolint:nilerr | ||
} | ||
|
||
dirEntries, err := os.ReadDir(snippetFolder) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
files := make([]string, 0) | ||
|
||
for _, dirEntry := range dirEntries { | ||
if dirEntry.IsDir() { | ||
continue | ||
} | ||
|
||
if !strings.HasSuffix(dirEntry.Name(), ".json") { | ||
continue | ||
} | ||
|
||
files = append(files, path.Join(snippetFolder, dirEntry.Name())) | ||
} | ||
|
||
if len(files) == 1 { | ||
// We have no other file to compare against | ||
return nil | ||
} | ||
|
||
var mainFile string | ||
|
||
for _, file := range files { | ||
if strings.HasSuffix(filepath.Base(file), "en-GB.json") { | ||
mainFile = file | ||
} | ||
} | ||
|
||
if len(mainFile) == 0 { | ||
context.AddWarning(fmt.Sprintf("No en-GB.json file found in %s, using %s", snippetFolder, files[0])) | ||
mainFile = files[0] | ||
} | ||
|
||
mainFileContent, err := os.ReadFile(mainFile) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
for _, file := range files { | ||
// makes no sense to compare to ourself | ||
if file == mainFile { | ||
continue | ||
} | ||
|
||
if err := compareSnippets(mainFileContent, file, context, extensionRoot); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func validateAdministrationSnippets(context *ValidationContext) { | ||
rootDir := context.Extension.GetRootDir() | ||
if err := validateAdministrationByPath(rootDir, context); err != nil { | ||
return | ||
} | ||
|
||
for _, extraBundle := range context.Extension.GetExtensionConfig().Build.ExtraBundles { | ||
bundlePath := rootDir | ||
|
||
if extraBundle.Path != "" { | ||
bundlePath = path.Join(bundlePath, extraBundle.Path) | ||
} else { | ||
bundlePath = path.Join(bundlePath, extraBundle.Name) | ||
} | ||
|
||
if err := validateAdministrationByPath(bundlePath, context); err != nil { | ||
return | ||
} | ||
} | ||
} | ||
|
||
func validateAdministrationByPath(extensionRoot string, context *ValidationContext) error { | ||
adminFolder := path.Join(extensionRoot, "Resources", "app", "administration") | ||
|
||
if _, err := os.Stat(adminFolder); err != nil { | ||
return nil //nolint:nilerr | ||
} | ||
|
||
snippetFiles := make(map[string][]string) | ||
|
||
err := filepath.WalkDir(adminFolder, func(path string, d os.DirEntry, err error) error { | ||
if d.IsDir() { | ||
return nil | ||
} | ||
|
||
if filepath.Ext(path) != ".json" { | ||
return nil | ||
} | ||
|
||
containingFolder := filepath.Dir(path) | ||
|
||
if filepath.Base(containingFolder) != "snippet" { | ||
return nil | ||
} | ||
|
||
if _, ok := snippetFiles[containingFolder]; !ok { | ||
snippetFiles[containingFolder] = []string{} | ||
} | ||
|
||
snippetFiles[containingFolder] = append(snippetFiles[containingFolder], path) | ||
|
||
return nil | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
for folder, files := range snippetFiles { | ||
if len(files) == 1 { | ||
// We have no other file to compare against | ||
continue | ||
} | ||
|
||
var mainFile string | ||
|
||
for _, file := range files { | ||
if strings.HasSuffix(filepath.Base(file), "en-GB.json") { | ||
mainFile = file | ||
} | ||
} | ||
|
||
if len(mainFile) == 0 { | ||
context.AddWarning(fmt.Sprintf("No en-GB.json file found in %s, using %s", folder, files[0])) | ||
mainFile = files[0] | ||
} | ||
|
||
mainFileContent, err := os.ReadFile(mainFile) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
for _, file := range files { | ||
// makes no sense to compare to ourself | ||
if file == mainFile { | ||
continue | ||
} | ||
|
||
if err := compareSnippets(mainFileContent, file, context, extensionRoot); err != nil { | ||
return err | ||
} | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func compareSnippets(mainFile []byte, file string, context *ValidationContext, extensionRoot string) error { | ||
checkFile, err := os.ReadFile(file) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
compare, err := jsondiff.CompareJSON(mainFile, checkFile) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
for _, diff := range compare { | ||
normalizedPath := strings.ReplaceAll(file, extensionRoot+"/", "") | ||
|
||
if diff.Type == jsondiff.OperationReplace && reflect.TypeOf(diff.OldValue) != reflect.TypeOf(diff.Value) { | ||
context.AddError(fmt.Sprintf("Snippet file: %s, key: %s, has the type %s, but in the main language it is %s", normalizedPath, diff.Path, reflect.TypeOf(diff.OldValue), reflect.TypeOf(diff.Value))) | ||
continue | ||
} | ||
|
||
if diff.Type == jsondiff.OperationAdd { | ||
context.AddError(fmt.Sprintf("Snippet file: %s, missing key \"%s\" in this snippet file, but defined in the main language", normalizedPath, diff.Path)) | ||
continue | ||
} | ||
|
||
if diff.Type == jsondiff.OperationRemove { | ||
context.AddError(fmt.Sprintf("Snippet file: %s, key: %s, is not defined in the main language", normalizedPath, diff.Path)) | ||
continue | ||
} | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package extension | ||
|
||
import ( | ||
"os" | ||
"path" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestSnippetValidateNoExistingFolderAdmin(t *testing.T) { | ||
context := newValidationContext(PlatformPlugin{ | ||
path: "test", | ||
config: &Config{}, | ||
}) | ||
|
||
validateAdministrationSnippets(context) | ||
} | ||
|
||
func TestSnippetValidateNoExistingFolderStorefront(t *testing.T) { | ||
context := newValidationContext(PlatformPlugin{ | ||
path: "test", | ||
config: &Config{}, | ||
}) | ||
|
||
validateAdministrationSnippets(context) | ||
} | ||
|
||
func TestSnippetValidateStorefrontByPathOneFileIsIgnored(t *testing.T) { | ||
tmpDir := t.TempDir() | ||
|
||
context := newValidationContext(PlatformPlugin{ | ||
path: tmpDir, | ||
config: &Config{}, | ||
}) | ||
|
||
_ = os.MkdirAll(path.Join(tmpDir, "Resources", "snippet"), os.ModePerm) | ||
_ = os.WriteFile(path.Join(tmpDir, "Resources", "snippet", "storefront.en-GB.json"), []byte(`{}`), os.ModePerm) | ||
|
||
assert.NoError(t, validateStorefrontSnippetsByPath(tmpDir, context)) | ||
assert.Len(t, context.errors, 0) | ||
assert.Len(t, context.warnings, 0) | ||
} | ||
|
||
func TestSnippetValidateStorefrontByPathSameFile(t *testing.T) { | ||
tmpDir := t.TempDir() | ||
|
||
context := newValidationContext(PlatformPlugin{ | ||
path: tmpDir, | ||
config: &Config{}, | ||
}) | ||
|
||
_ = os.MkdirAll(path.Join(tmpDir, "Resources", "snippet"), os.ModePerm) | ||
_ = os.WriteFile(path.Join(tmpDir, "Resources", "snippet", "storefront.en-GB.json"), []byte(`{"test": "1"}`), os.ModePerm) | ||
_ = os.WriteFile(path.Join(tmpDir, "Resources", "snippet", "storefront.de-DE.json"), []byte(`{"test": "2"}`), os.ModePerm) | ||
|
||
assert.NoError(t, validateStorefrontSnippetsByPath(tmpDir, context)) | ||
assert.Len(t, context.errors, 0) | ||
assert.Len(t, context.warnings, 0) | ||
} | ||
|
||
func TestSnippetValidateStorefrontByPathTestDifferent(t *testing.T) { | ||
tmpDir := t.TempDir() | ||
|
||
context := newValidationContext(PlatformPlugin{ | ||
path: tmpDir, | ||
config: &Config{}, | ||
}) | ||
|
||
_ = os.MkdirAll(path.Join(tmpDir, "Resources", "snippet"), os.ModePerm) | ||
_ = os.WriteFile(path.Join(tmpDir, "Resources", "snippet", "storefront.en-GB.json"), []byte(`{"a": "1"}`), os.ModePerm) | ||
_ = os.WriteFile(path.Join(tmpDir, "Resources", "snippet", "storefront.de-DE.json"), []byte(`{"b": "2"}`), os.ModePerm) | ||
|
||
assert.NoError(t, validateStorefrontSnippetsByPath(tmpDir, context)) | ||
assert.Len(t, context.errors, 2) | ||
assert.Len(t, context.warnings, 0) | ||
assert.Equal(t, "Snippet file: Resources/snippet/storefront.de-DE.json, key: /a, is not defined in the main language", context.errors[0]) | ||
assert.Equal(t, "Snippet file: Resources/snippet/storefront.de-DE.json, missing key \"/b\" in this snippet file, but defined in the main language", context.errors[1]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters