diff --git a/.env.template b/.env.template
new file mode 100644
index 0000000..89fc349
--- /dev/null
+++ b/.env.template
@@ -0,0 +1,10 @@
+FS_API="https://api.flagship.io"
+FS_AUTH_API="https://auth.flagship.io"
+REPOSITORY_BRANCH="master"
+DIRECTORY="path/to/directory"
+REPOSITORY_URL="https://gitlab.com/org/repo"
+NB_CODE_LINES_EDGES=1
+FLAGSHIP_CLIENT_ID=FLAGSHIP_MANAGEMENT_API_CLIENT_ID
+FLAGSHIP_CLIENT_SECRET=FLAGSHIP_MANAGEMENT_API_CLIENT_SECRET
+ACCOUNT_ID=FLAGSHIP_ACCOUNT_ID
+ENVIRONMENT_ID=FLAGSHIP_ENVIRONMENT_ID
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 8d2333a..9faae13 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
example/src-js
.env
coverage
+codebase-analyzer
diff --git a/README.md b/README.md
index c5a7fc8..e728424 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,7 @@ export ENVIRONMENT_ID=FLAGSHIP_ENVIRONMENT_ID
export REPOSITORY_URL=https://gitlab.com/org/repo
export REPOSITORY_BRANCH=master
export DIRECTORY=./
-./code-analyzer
+./codebase-analyzer
```
### With Docker
@@ -41,6 +41,7 @@ docker run -v $(pwd)/your_repo:/your_repo -e FLAGSHIP_CLIENT_ID=FLAGSHIP_MANAGEM
```
### With Homebrew
+
```sh
export FLAGSHIP_CLIENT_ID=FLAGSHIP_MANAGEMENT_API_CLIENT_ID
export FLAGSHIP_CLIENT_SECRET=FLAGSHIP_MANAGEMENT_API_CLIENT_SECRET
@@ -57,6 +58,7 @@ codebase-analyzer
```
### Supported file languages
+
- .cs .fs
- .dart
- .go
@@ -140,7 +142,7 @@ analyze_flag_references:
ACCOUNT_ID: YOUR_ACCOUNT_ID
ENVIRONMENT_ID: YOUR_ENVIRONMENT_ID
script:
- - /root/code-analyser
+ - /root/codebase-analyser
only:
- master
```
@@ -159,13 +161,12 @@ This repository needs go v1.13+ to work
2. Create a .env file to customize your environment variable
3. Run `go run *.go` in the example folder to run the code
-### Test
+### Test
```
make test
```
-
## Contributors
- Guillaume Jacquart [@GuillaumeJacquart](https://github.com/guillaumejacquart)
@@ -178,23 +179,25 @@ make test
[Apache License.](https://github.com/flagship-io/codebase-analyzer/blob/master/LICENSE)
## About Flagship
+
[Flagship by AB Tasty](https://www.flagship.io/) is a feature flagging platform for modern engineering and product teams. It eliminates the risks of future releases by separating code deployments from these releases :bulb: With Flagship, you have full control over the release process. You can:
+
- Switch features on or off through remote config.
- Automatically roll-out your features gradually to monitor performance and gather feedback from your most relevant users.
- Roll back any feature should any issues arise while testing in production.
- Segment users by granting access to a feature based on certain user attributes.
- Carry out A/B tests by easily assigning feature variations to groups of users.
-
-
-
-Flagship also allows you to choose whatever implementation method works for you from our many available SDKs or directly through a REST API. Additionally, our architecture is based on multi-cloud providers that offer high performance and highly-scalable managed services.
-
-**To learn more:**
-
+
+
+
+ Flagship also allows you to choose whatever implementation method works for you from our many available SDKs or directly through a REST API. Additionally, our architecture is based on multi-cloud providers that offer high performance and highly-scalable managed services.
+
+ **To learn more:**
+
- [Solution overview](https://www.flagship.io/#showvideo) - A 5mn video demo :movie_camera:
- [Documentation](https://docs.developers.flagship.io/) - Our dev portal with guides, how tos, API and SDK references
- [Sign up for a free trial](https://www.flagship.io/sign-up/) - Create your free account
diff --git a/example/src/flutter/sample.dart b/example/src/flutter/SDK_V3/sample.dart
similarity index 100%
rename from example/src/flutter/sample.dart
rename to example/src/flutter/SDK_V3/sample.dart
diff --git a/example/src/flutter/SDK_V4/sample.dart b/example/src/flutter/SDK_V4/sample.dart
new file mode 100644
index 0000000..87164c0
--- /dev/null
+++ b/example/src/flutter/SDK_V4/sample.dart
@@ -0,0 +1,18 @@
+import 'package:flagship/flagship.dart';
+
+// Step 1 - Start the Flagship sdk with default configuration.
+Flagship.start("_ENV_ID_", "_API_KEY_");
+
+// Step 2 - Create visitor with context "isVip" : true
+var visitor = Flagship.newVisitor(visitorId: "visitorId", hasConsented: true)
+ .withContext({"isVip": true}).build();
+
+// Step 3 - Fetch flags
+ visitor.fetchFlags().whenComplete(() {
+ // Step 4 - Get Flag key
+ var flag = v.getFlag("displayVipFeature");
+ // Step 5 - Read Flag value
+ var value = flag.value(false);
+ var value1 = v.getFlag("backgroundColor").value("red");
+ var value1 = v.getFlag("backgroundSize").value(1);
+ });
\ No newline at end of file
diff --git a/example/src/ios/SDK_V4/sample.swift b/example/src/ios/SDK_V4/sample.swift
new file mode 100644
index 0000000..69efc3f
--- /dev/null
+++ b/example/src/ios/SDK_V4/sample.swift
@@ -0,0 +1,30 @@
+import Flagship
+
+// Step 1 - Start the Flagship sdk with default configuration.
+Flagship.sharedInstance.start(envId: "_ENV_ID_", apiKey: "_API_KEY_")
+
+// Step 2 - Create visitor with context "isVip" : true
+let visitor = Flagship.sharedInstance.newVisitor(visitorId: "visitorId", hasConsented: true)
+ .withContext(context: ["isVip": true])
+ .build()
+
+// Step 3 - Fetch flags
+visitor.fetchFlags {
+
+ // Fetch completed
+
+ // Step 4 - Get Flag key
+ let flag = visitor.getFlag(key: "btnColor")
+
+ // Step 5 - Read Flag value
+ let value = flag.value(defaultValue: "red")
+
+ let value = visitor.getFlag(key: "displayVipFeature").value(defaultValue: false)
+
+ // Step 4 - Get Flag key
+ let flag2 = visitor.getFlag(key: "vipFeature")
+
+ // Step 5 - Read Flag value
+ let value = flag2.value(defaultValue: 16)
+
+}
\ No newline at end of file
diff --git a/example/src/java/SDK_V4/sample.kt b/example/src/java/SDK_V4/sample.kt
new file mode 100644
index 0000000..6ac5d00
--- /dev/null
+++ b/example/src/java/SDK_V4/sample.kt
@@ -0,0 +1,7 @@
+val flagRank = visitor.getFlag("btnColor")
+val flagRankValue = flagRank.value("red")
+
+val flagRankValue2 = visitor.getFlag("backgroundSize").value(1)
+
+val flagRank1 = visitor.getFlag("showBackground")
+val flagRankValue = flagRank1.value(true)
\ No newline at end of file
diff --git a/example/src/js/SDK_V4/sample.js b/example/src/js/SDK_V4/sample.js
new file mode 100644
index 0000000..67dfb30
--- /dev/null
+++ b/example/src/js/SDK_V4/sample.js
@@ -0,0 +1,75 @@
+//start demo
+// Usage: node demo/index.js
+const express = require("express");
+const { Flagship, HitType, EventCategory } = require("@flagship.io/js-sdk");
+
+const app = express();
+app.use(express.json());
+
+const visitorId = "visitor-id";
+
+// Step 1: Start the Flagship SDK by providing the environment ID and API key
+Flagship.start("", "", {
+ fetchNow: false,
+});
+
+// Endpoint to get an item
+app.get("/item", async (req, res) => {
+
+ // Step 2: Create a new visitor with a visitor ID and consent status
+ const visitor = Flagship.newVisitor({
+ visitorId,
+ hasConsented: true,
+ context: {
+ fs_is_vip: true,
+ },
+ });
+
+ // Step 3: Fetch the flags for the visitor
+ await visitor.fetchFlags();
+
+ // fe:flag:fs_disable_coupon, string
+ const fsDisableCoupon = visitor.getFlag("fs_disable_coupon");
+
+ // Step 4: Get the values of the flags for the visitor
+ const fsEnableDiscount = visitor.getFlag("fs_enable_discount");
+ const fsAddToCartBtnColor = visitor.getFlag("fs_add_to_cart_btn_color");
+
+ const fsEnableDiscountValue = fsEnableDiscount.getValue(false);
+ const fsAddToCartBtnColorValue = fsAddToCartBtnColor.getValue("blue");
+
+ res.json({
+ item: {
+ name: "Flagship T-shirt",
+ price: 20,
+ },
+ fsEnableDiscount: fsEnableDiscountValue,
+ fsAddToCartBtnColor: fsAddToCartBtnColorValue,
+ });
+});
+
+// Endpoint to add an item to the cart
+app.post("/add-to-cart", async (req, res) => {
+
+ const visitor = Flagship.newVisitor({
+ visitorId,
+ hasConsented: true
+ });
+
+ // Step 5: Send a hit to track an action
+ visitor.sendHit({
+ type: HitType.EVENT,
+ category: EventCategory.ACTION_TRACKING,
+ action: "add-to-cart-clicked",
+ });
+
+ res.json(null);
+});
+
+const port = 3000;
+
+app.listen(port, () => {
+ console.log(`Server running on port ${port}`);
+});
+
+//end demo
\ No newline at end of file
diff --git a/example/src/js/SDK_V4/sample.ts b/example/src/js/SDK_V4/sample.ts
new file mode 100644
index 0000000..76a54da
--- /dev/null
+++ b/example/src/js/SDK_V4/sample.ts
@@ -0,0 +1,29 @@
+import { Flagship } from "@flagship.io/js-sdk";
+
+Flagship.start("your_env_id", "your_api_key");
+
+const visitor = Flagship.newVisitor({
+ visitorId: "your_visitor_id",
+ context: { isVip: true },
+});
+
+visitor.fetchFlags();
+
+const variableKey = visitor.getFlag("flagKey");
+const boxSizeFlagDefaultValue = variableKey.getValue("flagDefaultValue");
+
+const variableKey1: any = visitor.getFlag("flagKey1");
+
+const variableKey3: any = visitor.getFlag("flagKey3");
+const boxSizeFlagDefaultValue1 = variableKey3.getValue(16);
+
+const variableKey4: any = visitor.getFlag("flagKey4");
+
+const variableKey5: any = visitor.getFlag("flagKey5").getValue(false);
+
+// fe:flag: flagKey6, true
+const variable6: any = visitor.getFlag("flagKey6");
+
+visitor.getFlag("flagKey5").getValue(false);
+
+visitor.getFlagValue("FlagKey5", false);
diff --git a/example/src/net/SDK_V4/sample.cs b/example/src/net/SDK_V4/sample.cs
new file mode 100644
index 0000000..a9eaaec
--- /dev/null
+++ b/example/src/net/SDK_V4/sample.cs
@@ -0,0 +1,24 @@
+using Flagship.Main;
+
+//Step 2: Create a visitor
+var visitor = Fs.NewVisitor("", true)
+ .SetContext(new Dictionary(){["isVip"] = true})
+ .Build();
+
+
+//Step 3: Fetch flag from the Flagship platform
+await visitor.FetchFlags();
+
+/* Step 4: Retrieves a flag named "displayVipFeature",
+ */
+var flag = visitor.GetFlag("showBtn");
+
+//Step 5: get the flag value and if the flag does not exist, it returns the default value "false"
+var flagValue = flag.GetValue(false);
+
+var flag_ = visitor.GetFlag("btnSize").GetValue(15);
+
+var flag1 = visitor.GetFlag("btnColor");
+var flagValue = flag1.GetValue("red");
+
+Console.WriteLine($"Flag {flagValue}");
\ No newline at end of file
diff --git a/example/src/net/SDK_V4/sample.fs b/example/src/net/SDK_V4/sample.fs
new file mode 100644
index 0000000..d0df006
--- /dev/null
+++ b/example/src/net/SDK_V4/sample.fs
@@ -0,0 +1,20 @@
+open System.Collections.Generic
+open Flagship
+
+let client = FlagshipBuilder.Start("ENV_ID","API_KEY");
+
+let context = new Dictionary();
+context.Add("key", "value");
+
+let visitor = client.NewVisitor("visitor_id", context);
+
+visitor.FetchFlags();
+
+let btnColorFlag = visitor.GetFlag("btnColor");
+let btnColorFlagValue = btnColorFlag.GetValue('red');
+
+let flag = visitor.GetFlag("btnSize");
+let flagValue = flag.GetValue(13);
+
+let showBtnFlag = visitor.GetFlag("showBtn");
+let showBtnFlagValue = showBtnFlag.GetValue(true);
\ No newline at end of file
diff --git a/example/src/php/SDK_V4/sample.php b/example/src/php/SDK_V4/sample.php
new file mode 100644
index 0000000..05b87bd
--- /dev/null
+++ b/example/src/php/SDK_V4/sample.php
@@ -0,0 +1,25 @@
+use Flagship\Flagship;
+
+// Step 1: start the SDK
+Flagship::start("", "");
+
+ //Step 2: Create a visitor
+ $visitor = Flagship::newVisitor("", true)
+ ->setContext(["isVip" => true])
+ ->build();
+
+ //Step 3: Fetch flag from the Flagship platform
+ $visitor->fetchFlags();
+
+ //Step 4: Retrieves a flag named "displayVipFeature"
+ $flag = $visitor->getFlag("displayVipFeature");
+
+ //Step 5: Returns the flag value ,or if the flag does not exist, it returns the default value "false"
+ echo "flag value:". $flag->getValue(false);
+
+ $flag1 = $visitor->getFlag("vipFeatureSize")->getValue(15);
+
+ $flag1 = $visitor->getFlag("vipFeatureColor")->getValue("red");
+
+ //Step 6: Batch all the collected hits and send them
+ Flagship::close();
\ No newline at end of file
diff --git a/example/src/react/SDK_V3/sample.jsx b/example/src/react/SDK_V3/sample.jsx
index f985916..4163471 100644
--- a/example/src/react/SDK_V3/sample.jsx
+++ b/example/src/react/SDK_V3/sample.jsx
@@ -4,7 +4,7 @@ import {useFsFlag} from "@flagship.io/react-sdk";
export const MyReactComponent = () => {
const backgroundColorFlag = useFsFlag("backgroundColor", "green")
const btnColorFlag = useFsFlag("btnColor", "red")
- const backgroundSize = useFsFlag("backgroundColor", 16)
+ const backgroundSize = useFsFlag("backgroundSize", 16)
const showBtn = useFsFlag("showBtn", true)
return (
diff --git a/example/src/react/SDK_V4/sample.jsx b/example/src/react/SDK_V4/sample.jsx
new file mode 100644
index 0000000..7ac18b7
--- /dev/null
+++ b/example/src/react/SDK_V4/sample.jsx
@@ -0,0 +1,28 @@
+import React from "react";
+import { useFsFlag } from "@flagship.io/react-sdk";
+
+export const MyReactComponent = () => {
+ ///Step 2: Retrieves a flag named "backgroundColor"
+ const flag = useFsFlag("backgroundColor")
+
+ //Step 3: Returns the value of the flag or if the flag does not exist, it returns the default value "green"
+ const flagValue = flag.getValue("green")
+
+
+ const flag_ = useFsFlag("btnSize").getValue(16)
+
+ const flag1 = useFsFlag("showBtn")
+ const flagValue_ = flag1.getValue(true)
+
+ return (
+
+ );
+};
\ No newline at end of file
diff --git a/internal/api/syncFlags.go b/internal/api/syncFlags.go
index 51aae11..264d2a8 100644
--- a/internal/api/syncFlags.go
+++ b/internal/api/syncFlags.go
@@ -4,7 +4,7 @@ import (
"bytes"
"encoding/json"
"fmt"
- "io/ioutil"
+ "io"
"net/http"
log "github.com/sirupsen/logrus"
@@ -117,7 +117,7 @@ func generateAuthenticationToken(cfg *config.Config) (string, error) {
defer resp.Body.Close()
var result AuthResponse
- body, err := ioutil.ReadAll(resp.Body)
+ body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal("Error while reading body", err.Error())
@@ -129,7 +129,7 @@ func generateAuthenticationToken(cfg *config.Config) (string, error) {
return result.AccessToken, nil
} else {
- body, _ := ioutil.ReadAll(resp.Body)
+ body, _ := io.ReadAll(resp.Body)
return "", fmt.Errorf("error when calling Flagship authentication API. Status: %s, body: %s", resp.Status, string(body))
}
}
@@ -167,7 +167,7 @@ func callAPI(cfg *config.Config, flagInfos FlagUsageRequest) error {
}
if resp.StatusCode != 201 {
- body, err := ioutil.ReadAll(resp.Body)
+ body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal("Error while reading body", err.Error())
diff --git a/internal/files/search.go b/internal/files/search.go
index ea36ba6..40bb05a 100644
--- a/internal/files/search.go
+++ b/internal/files/search.go
@@ -3,17 +3,44 @@ package files
import (
"encoding/json"
"fmt"
- "io/ioutil"
+ "os"
"path/filepath"
"regexp"
"strings"
"github.com/flagship-io/codebase-analyzer/internal/model"
"github.com/flagship-io/codebase-analyzer/pkg/config"
- "github.com/sirupsen/logrus"
+ log "github.com/sirupsen/logrus"
"github.com/thoas/go-funk"
)
+type MatchVariableFlag struct {
+ Variable string
+ FlagKey string
+ CodeLines string
+ CodeLineHighlight int
+ CodeLineURL string
+ LineNumber int
+}
+
+type MatchVariableDefaultValue struct {
+ Variable string
+ FlagDefaultValue string
+ FlagType string
+}
+
+type RegexStruct struct {
+ isFlag bool
+ isDefaultValue bool
+ regex []string
+}
+
+type FlagIndexesStruct struct {
+ isFlag bool
+ isDefaultValue bool
+ FlagIndexes [][]int
+}
+
func GetFlagType(defaultValue string) (string, string) {
var flagType string = "string"
@@ -54,7 +81,7 @@ func GetFlagType(defaultValue string) (string, string) {
// SearchFiles search code pattern in files and return results and error
func SearchFiles(cfg *config.Config, path string, resultChannel chan model.FileSearchResult) {
// Read file contents
- fileContent, err := ioutil.ReadFile(path)
+ fileContent, err := os.ReadFile(path)
if err != nil {
resultChannel <- model.FileSearchResult{
File: path,
@@ -63,18 +90,30 @@ func SearchFiles(cfg *config.Config, path string, resultChannel chan model.FileS
}
return
}
+
fileContentStr := strings.ReplaceAll(string(fileContent), "\r\n", "\n")
// Get file extension to choose matching regex
ext := filepath.Ext(path)
- var regexes []string
+ var regexesNotSplit []string
+ var regexesSplit []RegexStruct
for _, extRegex := range model.LanguageRegexes {
- regxp := regexp.MustCompile(extRegex.FileExtension)
- if regxp.Match([]byte(ext)) {
- regexes = append(regexes, extRegex.Regexes...)
+ if !extRegex.Split {
+ regxp := regexp.MustCompile(extRegex.FileExtension)
+ if regxp.Match([]byte(ext)) {
+ regexesNotSplit = append(regexesNotSplit, extRegex.Regexes...)
+ }
+ }
+
+ if extRegex.Split {
+ regxp := regexp.MustCompile(extRegex.FileExtension)
+ if regxp.Match([]byte(ext)) {
+ regexesSplit = append(regexesSplit, RegexStruct{isFlag: extRegex.ForFlag, isDefaultValue: extRegex.ForDefaultValue, regex: extRegex.Regexes})
+ }
}
}
- if len(regexes) == 0 {
+
+ if len(regexesNotSplit) == 0 && len(regexesSplit) == 0 {
resultChannel <- model.FileSearchResult{
File: path,
Results: nil,
@@ -84,14 +123,15 @@ func SearchFiles(cfg *config.Config, path string, resultChannel chan model.FileS
}
// Add default regex for flags in commentaries
- regexes = append(regexes,
- `fs:flag:(.+)`,
+ regexesNotSplit = append(regexesNotSplit,
+ `fe:flag:\s*(\w+)\s*[,]\s*(\w+)\s*`,
)
- results := []model.SearchResult{}
+ resultsNotSplit := []model.SearchResult{}
+ flagIndexesNotSplit := [][]int{}
+ flagIndexesSplit := flagIndexSplitter(fileContentStr, path, regexesSplit)
- flagIndexes := [][]int{}
- for _, regex := range regexes {
+ for _, regex := range regexesNotSplit {
regxp := regexp.MustCompile(regex)
flagLineIndexes := regxp.FindAllStringIndex(fileContentStr, -1)
@@ -102,24 +142,24 @@ func SearchFiles(cfg *config.Config, path string, resultChannel chan model.FileS
for _, submatchIndex := range submatchIndexes {
if len(submatchIndex) < 3 {
- logrus.WithFields(logrus.Fields{
+ log.WithFields(log.Fields{
"reason": fmt.Sprintf("Did not find the flag key in file %s. Code: %s", path, submatch),
}).Error("Key not found")
continue
}
if len(submatchIndex) < 6 {
- logrus.WithFields(logrus.Fields{
+ log.WithFields(log.Fields{
"reason": fmt.Sprintf("Did not find the flag default value in file %s. Code: %s", path, submatch),
}).Warn("Type unknown")
- flagIndexes = append(flagIndexes, []int{
+ flagIndexesNotSplit = append(flagIndexesNotSplit, []int{
flagLineIndex[0] + submatchIndex[2],
flagLineIndex[0] + submatchIndex[3],
})
continue
}
- flagIndexes = append(flagIndexes, []int{
+ flagIndexesNotSplit = append(flagIndexesNotSplit, []int{
flagLineIndex[0] + submatchIndex[2],
flagLineIndex[0] + submatchIndex[3],
flagLineIndex[0] + submatchIndex[4],
@@ -129,7 +169,60 @@ func SearchFiles(cfg *config.Config, path string, resultChannel chan model.FileS
}
}
- for _, flagIndex := range flagIndexes {
+ var flagMatches []MatchVariableFlag
+ var defaultValueMatches []MatchVariableDefaultValue
+
+ for _, flagIndexes := range flagIndexesSplit {
+ for _, flagIndex := range flagIndexes.FlagIndexes {
+ variable := fileContentStr[flagIndex[0]:flagIndex[1]]
+ if len(flagIndex) < 3 {
+ log.WithFields(log.Fields{
+ "reason": fmt.Sprintf("Did not find the flag key or default value in file %s", path),
+ }).Error("Key not found")
+ continue
+ }
+
+ keyOrDefaultValue := fileContentStr[flagIndex[2]:flagIndex[3]]
+
+ if flagIndexes.isFlag {
+ firstLineIndex := getSurroundingLineIndex(fileContentStr, flagIndex[0], true, cfg.NbLineCodeEdges)
+ lastLineIndex := getSurroundingLineIndex(fileContentStr, flagIndex[1], false, cfg.NbLineCodeEdges)
+ code := fileContentStr[firstLineIndex:lastLineIndex]
+ keyWrapped := keyWrapper(keyOrDefaultValue, fileContentStr, flagIndex)
+ lineNumber := getLineFromPos(fileContentStr, flagIndex[2])
+ codeLineHighlight := getLineFromPos(code, strings.Index(code, keyWrapped))
+ _ = codeLineHighlight
+
+ flagMatches = append(flagMatches, MatchVariableFlag{
+ Variable: variable,
+ FlagKey: keyOrDefaultValue,
+ CodeLines: code,
+ CodeLineHighlight: codeLineHighlight,
+ CodeLineURL: getCodeURL(cfg, path, &lineNumber),
+ LineNumber: lineNumber,
+ })
+ }
+
+ if flagIndexes.isDefaultValue {
+ flagType, defaultValue_ := GetFlagType(keyOrDefaultValue)
+
+ if variable == "" || keyOrDefaultValue == "" || defaultValue_ == "" {
+ flagType = "unknown"
+ }
+
+ if variable != "" {
+ defaultValueMatches = append(defaultValueMatches, MatchVariableDefaultValue{
+ Variable: variable,
+ FlagDefaultValue: keyOrDefaultValue,
+ FlagType: flagType,
+ })
+ }
+
+ }
+ }
+ }
+
+ for _, flagIndex := range flagIndexesNotSplit {
// Extract the code with a certain number of lines
defaultValue_ := ""
flagType := "unknown"
@@ -157,23 +250,30 @@ func SearchFiles(cfg *config.Config, path string, resultChannel chan model.FileS
lineNumber := getLineFromPos(fileContentStr, flagIndex[0])
codeLineHighlight := getLineFromPos(code, strings.Index(code, keyWrapper))
- results = append(results, model.SearchResult{
- FlagKey: key,
- FlagDefaultValue: defaultValue_,
- FlagType: flagType,
- CodeLines: code,
- CodeLineHighlight: codeLineHighlight,
- CodeLineURL: getCodeURL(cfg, path, &lineNumber),
- // Get line number of the code
- LineNumber: lineNumber,
- })
+ if key != "" {
+ resultsNotSplit = append(resultsNotSplit, model.SearchResult{
+ FlagKey: key,
+ FlagDefaultValue: defaultValue_,
+ FlagType: flagType,
+ CodeLines: code,
+ CodeLineHighlight: codeLineHighlight,
+ CodeLineURL: getCodeURL(cfg, path, &lineNumber),
+ LineNumber: lineNumber,
+ })
+ }
}
+ resultsSplit := matchFlagWithDefaultValue(flagMatches, defaultValueMatches)
+ combinedResults := append(resultsNotSplit, resultsSplit...)
+
+ res, _ := json.Marshal(combinedResults)
+ fmt.Println(string(res))
+
resultChannel <- model.FileSearchResult{
File: path,
FileURL: getCodeURL(cfg, path, nil),
- Results: results,
+ Results: combinedResults,
Error: err,
}
}
@@ -230,3 +330,96 @@ func getLineFromPos(input string, indexPosition int) int {
}
return lineNumber
}
+
+func keyWrapper(key, fileContentStr string, flagIndex []int) string {
+ keyWrapper := key
+ nbCharsWrapping := 5
+ if flagIndex[2] > nbCharsWrapping && flagIndex[3] < len(fileContentStr)-nbCharsWrapping {
+ keyWrapper = fileContentStr[flagIndex[2]-nbCharsWrapping : flagIndex[3]+nbCharsWrapping]
+ }
+ return keyWrapper
+}
+
+func flagIndexSplitter(fileContentStr, path string, regexesSplit []RegexStruct) []FlagIndexesStruct {
+ flagIndexesSplits := []FlagIndexesStruct{}
+ for _, regexSplit := range regexesSplit {
+ for _, regex := range regexSplit.regex {
+ var indexSplit [][]int
+ regxp := regexp.MustCompile(regex)
+ flagLineIndexes := regxp.FindAllStringIndex(fileContentStr, -1)
+ for _, flagLineIndex := range flagLineIndexes {
+ submatch := fileContentStr[flagLineIndex[0]:flagLineIndex[1]]
+ submatchIndexes := regxp.FindAllStringSubmatchIndex(submatch, -1)
+
+ for _, submatchIndex := range submatchIndexes {
+ if len(submatchIndex) < 3 {
+ log.WithFields(log.Fields{
+ "reason": fmt.Sprintf("Did not find the variable in file %s. Code: %s", path, submatch),
+ }).Error("Key not found")
+ continue
+ }
+
+ if len(submatchIndex) < 6 {
+ log.WithFields(log.Fields{
+ "reason": fmt.Sprintf("Did not find the flag key or default value in file %s. Code: %s", path, submatch),
+ }).Warn("Type unknown")
+ indexSplit = append(indexSplit, []int{
+ flagLineIndex[0] + submatchIndex[2],
+ flagLineIndex[0] + submatchIndex[3],
+ })
+ continue
+ }
+
+ indexSplit = append(indexSplit, []int{
+ flagLineIndex[0] + submatchIndex[2],
+ flagLineIndex[0] + submatchIndex[3],
+ flagLineIndex[0] + submatchIndex[4],
+ flagLineIndex[0] + submatchIndex[5],
+ })
+ }
+ }
+
+ flagIndexesSplits = append(flagIndexesSplits, FlagIndexesStruct{
+ isFlag: regexSplit.isFlag,
+ isDefaultValue: regexSplit.isDefaultValue,
+ FlagIndexes: indexSplit,
+ })
+ }
+ }
+
+ return flagIndexesSplits
+}
+
+func matchFlagWithDefaultValue(flagMatches []MatchVariableFlag, defaultValueMatches []MatchVariableDefaultValue) []model.SearchResult {
+ nameMap := make(map[string]MatchVariableDefaultValue)
+ results := []model.SearchResult{}
+
+ for _, p2 := range defaultValueMatches {
+ nameMap[p2.Variable] = p2
+ }
+
+ for _, p1 := range flagMatches {
+
+ searchResult := model.SearchResult{
+ FlagKey: p1.FlagKey,
+ FlagDefaultValue: "",
+ FlagType: "unknown",
+ CodeLines: p1.CodeLines,
+ CodeLineHighlight: p1.CodeLineHighlight,
+ CodeLineURL: p1.CodeLineURL,
+ LineNumber: p1.LineNumber,
+ }
+
+ if p2, found := nameMap[p1.Variable]; found {
+ searchResult.FlagType = p2.FlagType
+ searchResult.FlagDefaultValue = p2.FlagDefaultValue
+ }
+
+ if p1.FlagKey != "" {
+ results = append(results, searchResult)
+ }
+
+ }
+
+ return results
+}
diff --git a/internal/files/search_test.go b/internal/files/search_test.go
index e0ac215..78d3ee6 100644
--- a/internal/files/search_test.go
+++ b/internal/files/search_test.go
@@ -73,6 +73,14 @@ func TestSearchFiles(t *testing.T) {
{name: "vipFeature", lineNumber: 11, codeLineHighlight: 6},
},
},
+ {
+ filePath: "../../example/src/ios/SDK_V4/sample.swift",
+ flags: []flag{
+ {name: "displayVipFeature", lineNumber: 22, codeLineHighlight: 6},
+ {name: "btnColor", lineNumber: 17, codeLineHighlight: 6},
+ {name: "vipFeature", lineNumber: 25, codeLineHighlight: 6},
+ },
+ },
{
filePath: "../../example/src/java/SDK_V2/sample.java",
flags: []flag{
@@ -107,6 +115,14 @@ func TestSearchFiles(t *testing.T) {
{name: "showBackground", lineNumber: 12, codeLineHighlight: 6},
},
},
+ {
+ filePath: "../../example/src/java/SDK_V4/sample.kt",
+ flags: []flag{
+ {name: "backgroundSize", lineNumber: 4, codeLineHighlight: 4},
+ {name: "btnColor", lineNumber: 1, codeLineHighlight: 1},
+ {name: "showBackground", lineNumber: 6, codeLineHighlight: 6},
+ },
+ },
{
filePath: "../../example/src/js/SDK_V2/sample.js",
flags: []flag{
@@ -175,6 +191,14 @@ func TestSearchFiles(t *testing.T) {
{name: "showBtn", lineNumber: 17, codeLineHighlight: 6},
},
},
+ {
+ filePath: "../../example/src/net/SDK_V4/sample.cs",
+ flags: []flag{
+ {name: "btnSize", lineNumber: 19, codeLineHighlight: 6},
+ {name: "showBtn", lineNumber: 14, codeLineHighlight: 6},
+ {name: "btnColor", lineNumber: 21, codeLineHighlight: 6},
+ },
+ },
{
filePath: "../../example/src/net/SDK_V1/sample.vb",
flags: []flag{
@@ -212,10 +236,18 @@ func TestSearchFiles(t *testing.T) {
flags: []flag{
{name: "backgroundColor", lineNumber: 5, codeLineHighlight: 5},
{name: "btnColor", lineNumber: 6, codeLineHighlight: 6},
- {name: "backgroundColor", lineNumber: 7, codeLineHighlight: 6},
+ {name: "backgroundSize", lineNumber: 7, codeLineHighlight: 6},
{name: "showBtn", lineNumber: 8, codeLineHighlight: 6},
},
},
+ {
+ filePath: "../../example/src/react/SDK_V4/sample.jsx",
+ flags: []flag{
+ {name: "btnSize", lineNumber: 12, codeLineHighlight: 6},
+ {name: "backgroundColor", lineNumber: 6, codeLineHighlight: 6},
+ {name: "showBtn", lineNumber: 14, codeLineHighlight: 6},
+ },
+ },
{
filePath: "../../example/src/php/SDK_V1/sample.php",
flags: []flag{
@@ -241,13 +273,29 @@ func TestSearchFiles(t *testing.T) {
},
},
{
- filePath: "../../example/src/flutter/sample.dart",
+ filePath: "../../example/src/php/SDK_V4/sample.php",
+ flags: []flag{
+ {name: "vipFeatureSize", lineNumber: 20, codeLineHighlight: 6},
+ {name: "vipFeatureColor", lineNumber: 22, codeLineHighlight: 6},
+ {name: "displayVipFeature", lineNumber: 15, codeLineHighlight: 6},
+ },
+ },
+ {
+ filePath: "../../example/src/flutter/SDK_V3/sample.dart",
flags: []flag{
{name: "displayVipFeature", lineNumber: 15, codeLineHighlight: 6},
{name: "backgroundColor", lineNumber: 16, codeLineHighlight: 6},
{name: "backgroundSize", lineNumber: 17, codeLineHighlight: 6},
},
},
+ {
+ filePath: "../../example/src/flutter/SDK_V4/sample.dart",
+ flags: []flag{
+ {name: "backgroundColor", lineNumber: 16, codeLineHighlight: 6},
+ {name: "backgroundSize", lineNumber: 17, codeLineHighlight: 6},
+ {name: "displayVipFeature", lineNumber: 13, codeLineHighlight: 6},
+ },
+ },
}
resultChannel := make(chan model.FileSearchResult)
diff --git a/internal/model/language_regexes.go b/internal/model/language_regexes.go
index 0742fbe..2e39ac2 100644
--- a/internal/model/language_regexes.go
+++ b/internal/model/language_regexes.go
@@ -6,8 +6,11 @@ import (
)
type LanguageRegex struct {
- FileExtension string `json:"file_extension"`
- Regexes []string `json:"regexes"`
+ FileExtension string `json:"file_extension"`
+ Regexes []string `json:"regexes"`
+ Split bool `json:"split"`
+ ForFlag bool `json:"search_flag"`
+ ForDefaultValue bool `json:"search_default_value"`
}
var LanguageRegexes = []LanguageRegex{
@@ -15,11 +18,29 @@ var LanguageRegexes = []LanguageRegex{
FileExtension: `\.[jt]sx?$`,
Regexes: []string{
`useFsFlag[(](?:(?:\s*['"](.*)['"]\s*,\s*(["'].*\s*[^"]*["']|[^)]*))\s*[)])?`, // SDK React V3
+ `useFsFlag[(](?:(?:\s*["'](.*)["']\s*[)]\s*.getValue[(](["'].*\s*[^"]*["']|[^)]*))\s*[)])?`, // SDK React V4
`['"]?key['"]?\s*\:\s*['"](.+?)['"](?:.*\s*)['"]?defaultValue['"]?\s*\:\s*(['"].*['"]|[^\r\n\t\f\v,}]+).*[},]?`, // SDK JS V2 && SDK React V2
`getFlag[(](?:(?:\s*["'](.*)["']\s*,\s*(["'].*\s*[^"]*["']|[^)]*))\s*[)])?`, // SDK JS V3
+ `getFlag[(](?:(?:\s*["'](\w*)["']\s*[)]\s*.getValue[(](["']\w*\s*[^"]*["']|[^)]*))\s*[)])?`, // SDK JS V4
},
},
-
+ {
+ FileExtension: `\.[jt]sx?$`,
+ Regexes: []string{
+ `(?:(\w+)\s*[?]?\s*[:]?\s*(?:number|string|boolean|any|void| never|null|undefined|bigint|symbol|object|IFSFlag|FSFlag)?)\s*[=]\s*(?:.*)useFsFlag[(](?:(?:\s*["'](\w*)["']\s*[)]\s*))[^\.]`, // SDK React V4 Key
+ `(?:(\w+)\s*[?]?\s*[:]?\s*(?:number|string|boolean|any|void| never|null|undefined|bigint|symbol|object|IFSFlag|FSFlag)?)\s*[=]\s*(?:.*)getFlag[(](?:(?:\s*["'](\w*)["']\s*[)]\s*))[^\.]`, // SDK JS V4 Key
+ },
+ Split: true,
+ ForFlag: true,
+ },
+ {
+ FileExtension: `\.[jt]sx?$`,
+ Regexes: []string{
+ `\s*(\w*)[\.]getValue[(](["']?\w*["']?)[)]`, // SDK JS & React V4 Default value
+ },
+ Split: true,
+ ForDefaultValue: true,
+ },
{
FileExtension: `\.go$`,
Regexes: []string{
@@ -39,27 +60,78 @@ var LanguageRegexes = []LanguageRegex{
`\.getFlag[(](?:\s*(?:\s*["'](.*)["']\s*,\s*(["'].*\s*[^"]*["']|[^)]*))\s*[)])?`, // SDK JAVA V3
},
},
+ {
+ FileExtension: `\.kt$`,
+ Regexes: []string{
+ `\.getModification\(\s*(?:\s*["']([\w\-]+)['"]\s*,\s*(["'][^"]*['"]|[+-]?(?:\d*[.])?\d+|true|false|False|True)(?:\s*,\s*(?:true|false|\d+|"[^"]*"))?\s*\))?`, // SDK ANDROID V2
+ `\.getFlag[(](?:\s*(?:\s*["'](.*)["']\s*,\s*(["'].*\s*[^"]*["']|[^)]*))\s*[)])?`, // SDK ANDROID V3
+ `getFlag[(](?:(?:\s*["'](\w*)["']\s*[)]\s*.value[(](["'].*\s*[^"]*["']|[^)]*))\s*[)])?`, // SDK ANDROID V4
+
+ },
+ },
+ {
+ FileExtension: `\.kt$`,
+ Regexes: []string{
+ `(?:(\w+))\s*[=]\s*(?:.*)getFlag[(](?:(?:\s*["'](\w*)["']\s*[)]\s*))[^\.]`, // SDK ANDROID V4 Key
+ },
+ Split: true,
+ ForFlag: true,
+ },
+ {
+ FileExtension: `\.kt$`,
+ Regexes: []string{
+ `\s*(\w*)[\.]value[(](["']?\w*["']?)[)]`, // SDK ANDROID V4 Default value
+ },
+ Split: true,
+ ForDefaultValue: true,
+ },
{
FileExtension: `\.php$`,
Regexes: []string{
`\-\>getModification\(\s*(?:\s*["']([\w\-]+)['"]\s*,\s*(["'][^"]*['"]|[+-]?(?:\d*[.])?\d+|true|false|False|True)(?:\s*,\s*(?:true|false|\d+|"[^"]*"))?\s*\))?`, // SDK PHP V1 && SDK PHP V2
`\-\>getFlag[(](?:\s*(?:\s*["'](.*)["']\s*,\s*(["'].*\s*[^"]*["']|[^)]*))\s*[)])?`, // SDK PHP V3
+ `\-\>getFlag[(](?:\s*(?:\s*["'](\w*)["']\s*[)]\s*\-\>\s*getValue[(](["'].*\s*[^"]*["']|[^)]*))\s*[)])?`, // SDK PHP V4
},
},
{
- FileExtension: `\.kt$`,
+ FileExtension: `\.php$`,
Regexes: []string{
- `\.getModification\(\s*(?:\s*["']([\w\-]+)['"]\s*,\s*(["'][^"]*['"]|[+-]?(?:\d*[.])?\d+|true|false|False|True)(?:\s*,\s*(?:true|false|\d+|"[^"]*"))?\s*\))?`, // SDK ANDROID V2
- `\.getFlag[(](?:\s*(?:\s*["'](.*)["']\s*,\s*(["'].*\s*[^"]*["']|[^)]*))\s*[)])?`, // SDK ANDROID V3
-
+ `(?:(\w+))\s*[=]\s*(?:.*)getFlag[(](?:(?:\s*["'](\w*)["']\s*[)]\s*))[^\-]`, // SDK PHP V4 Key
},
+ Split: true,
+ ForFlag: true,
+ },
+ {
+ FileExtension: `\.php$`,
+ Regexes: []string{
+ `\s*(\w*)\s*\-\>\s*getValue[(](["']?\w*["']?)[)]`, // SDK PHP V4 Default value
+ },
+ Split: true,
+ ForDefaultValue: true,
},
{
FileExtension: `\.swift$`,
Regexes: []string{
`\.getModification\((?:\s*["'](\w+)['"]\s*,\s*default(?:String|Double|Float|Int|Bool|Json|Array)\s*:\s*(["'][^"]*['"]|[+-]?(?:\d*[.])?\d+|true|false|False|True)\s*(?:,\s*activate\s*:\s*(?:true|false|\d+|"[^"]*"))?\s*\))?`, // SDK iOS V2
`\.getFlag[(](?:\s*key\s*:\s*(?:\s*["'](.*)["']\s*,\s*defaultValue\s*:\s*(["'].*\s*[^"]*["']|[^)]*))\s*[)])?`, // SDK iOS V3
+ `getFlag[(](?:\s*key\s*[:]\s*(?:\s*["'](\w*)["']\s*[)]\s*\.value[(]\s*defaultValue\s*[:]\s*(["'].*\s*[^"]*["']|[^)]*))\s*[)])?`, // SDK iOS V4
+ },
+ },
+ {
+ FileExtension: `\.swift$`,
+ Regexes: []string{
+ `(?:(\w+))\s*[=]\s*(?:.*)getFlag[(](?:(?:\s*key\s*[:]\s*["'](\w*)["']\s*[)]\s*))[^\.]`, // SDK iOS V4 Key
},
+ Split: true,
+ ForFlag: true,
+ },
+ {
+ FileExtension: `\.swift$`,
+ Regexes: []string{
+ `\s*(\w*)[\.]value[(]\s*defaultValue\s*[:]\s*(["']?\w*["']?)[)]`, // SDK iOS V4 Default value
+ },
+ Split: true,
+ ForDefaultValue: true,
},
{
FileExtension: `\.m$`,
@@ -73,20 +145,55 @@ var LanguageRegexes = []LanguageRegex{
Regexes: []string{
`\.GetModification\((?:\s*["']([\w\-]+)['"]\s*,\s*(["'][^"]*['"]|[+-]?(?:\d*[.])?\d+|true|false|False|True)(?:\s*,\s*(?:true|false|\d+|"[^"]*"))?\s*\))?`, // SDK .NET V1
`\.GetFlag\((?:\s*["']([\w\-]+)['"]\s*,\s*(["'][^"]*['"]|[+-]?(?:\d*[.])?\d+|true|false|False|True)(?:\s*,\s*(?:true|false|\d+|"[^"]*"))?\s*\))?`, // SDK .NET V3
+ `GetFlag[(](?:(?:\s*["'](\w*)["']\s*[)]\s*.GetValue[(](["'].*\s*[^"]*["']|[^)]*))\s*[)])?`, // SDK .NET V4
+ },
+ },
+ {
+ FileExtension: `\.[fc]s$`,
+ Regexes: []string{
+ `(?:(\w+))\s*[=]\s*(?:.*)GetFlag[(](?:(?:\s*["'](\w*)["']\s*[)]\s*))[^\.]`, // SDK .NET V4 Key
},
+ Split: true,
+ ForFlag: true,
+ },
+ {
+ FileExtension: `\.[fc]s$`,
+ Regexes: []string{
+ `\s*(\w*)[\.]GetValue[(](["']?\w*["']?)[)]`, // SDK .NET V4 Default value
+ },
+ Split: true,
+ ForDefaultValue: true,
},
{
FileExtension: `\.vb$`,
Regexes: []string{
`\.GetModification\((?:\s*["']([\w\-]+)['"]\s*,\s*(["'][^"]*['"]|[+-]?(?:\d*[.])?\d+|true|false|False|True)(?:\s*,\s*(?:true|True|false|False|\d+|"[^"]*"))?\s*\))?`, // SDK .NET V1
`\.GetFlag\((?:\s*["']([\w\-]+)['"]\s*,\s*(["'][^"]*['"]|[+-]?(?:\d*[.])?\d+|true|false|False|True)(?:\s*,\s*(?:true|false|\d+|"[^"]*"))?\s*\))?`, // SDK .NET V3
+ `GetFlag[(](?:(?:\s*["'](\w*)["']\s*[)]\s*.GetValue[(](["'].*\s*[^"]*["']|[^)]*))\s*[)])?`, // SDK .NET V4
+ },
+ },
+ {
+ FileExtension: `\.dart$`,
+ Regexes: []string{
+ `getFlag[(](?:(?:\s*["'](.*)["']\s*,\s*(["'].*\s*[^"]*["']|[^)]*))\s*[)])?`, // SDK Flutter V1-V2-V3
+ `getFlag[(](?:(?:\s*["'](\w*)["']\s*[)]\s*.value[(](["'].*\s*[^"]*["']|[^)]*))\s*[)])?`, // SDK Flutter V4
+ },
+ },
+ {
+ FileExtension: `\.dart$`,
+ Regexes: []string{
+ `(?:(\w+))\s*[=]\s*(?:.*)getFlag[(](?:(?:\s*["'](\w*)["']\s*[)]\s*))[^\.]`, // SDK Flutter V4 Key
},
+ Split: true,
+ ForFlag: true,
},
{
FileExtension: `\.dart$`,
Regexes: []string{
- `getFlag[(](?:(?:\s*["'](.*)["']\s*,\s*(["'].*\s*[^"]*["']|[^)]*))\s*[)])?`, // SDK Flutter V1-V2-V3
+ `\s*(\w*)[\.]value[(](["']?\w*["']?)[)]`, // SDK Flutter V4 Default value
},
+ Split: true,
+ ForDefaultValue: true,
},
}