├── .editorconfig ├── .github └── workflows │ ├── publish-gradle-plugin.yml │ ├── publish-intellij-plugin.yml │ └── pull-request.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dangerfile ├── Gemfile ├── LICENSE ├── NOTICE.md ├── README.md ├── build.gradle.kts ├── feature-flag ├── build.gradle.kts └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── linecorp │ │ └── android │ │ └── featureflag │ │ ├── FeatureFlagExtension.kt │ │ ├── FeatureFlagJavaFileWriter.kt │ │ ├── FeatureFlagPlugin.kt │ │ ├── FeatureFlagTask.kt │ │ ├── loader │ │ ├── FeatureFlagFileTokenizer.kt │ │ ├── FeatureFlagOptionParser.kt │ │ ├── FeatureFlagSelectorEvaluator.kt │ │ ├── FeatureFlagSelectorParser.kt │ │ └── FeatureFlagValueOptimizer.kt │ │ ├── model │ │ ├── BuildEnvironment.kt │ │ ├── BuildVariant.kt │ │ ├── DisjunctionNormalForm.kt │ │ ├── FeatureFlagAppliedElement.kt │ │ ├── FeatureFlagData.kt │ │ ├── FeatureFlagElement.kt │ │ ├── FeatureFlagEntry.kt │ │ ├── FeatureFlagOption.kt │ │ ├── FlagLink.kt │ │ └── ForciblyOverriddenFeatureFlags.kt │ │ └── util │ │ └── Extensions.kt │ └── test │ ├── kotlin │ └── com │ │ └── linecorp │ │ └── android │ │ └── featureflag │ │ ├── FeatureFlagExtensionTest.kt │ │ ├── FeatureFlagPluginTest.kt │ │ ├── loader │ │ ├── FeatureFlagFileTokenizerTest.kt │ │ ├── FeatureFlagOptionParserTest.kt │ │ ├── FeatureFlagSelectorEvaluatorTest.kt │ │ ├── FeatureFlagSelectorParserTest.kt │ │ └── FeatureFlagValueOptimizerTest.kt │ │ └── utils │ │ ├── AssertUtils.kt │ │ └── TestUtils.kt │ └── resources │ └── tests │ ├── FeatureFlagFileTokenizerTest │ ├── FLAG_INVALID_BLANK_KEY │ ├── FLAG_INVALID_BLANK_VALUE │ ├── FLAG_INVALID_EMPTY_KEY │ ├── FLAG_INVALID_EMPTY_VALUE │ ├── FLAG_INVALID_NO_KEY_VALUE │ ├── FLAG_VALID_EMPTY │ ├── FLAG_VALID_NAME │ ├── FLAG_VALID_OPTION │ └── FLAG_VALID_VALUE │ ├── FeatureFlagOptionParserTest │ ├── OPTION_INVALID_UNDEFINED_OPTION │ ├── OPTION_VALID_DUPLICATED │ ├── OPTION_VALID_NORMAL │ └── OPTION_VALID_SPACE │ └── FeatureFlagSelectorParserTest │ ├── VALUE_INVALID_CONJUNCTION_EMPTY │ ├── VALUE_INVALID_CONJUNCTION_ONE_SIDE │ ├── VALUE_INVALID_DISJUNCTION_EMPTY │ ├── VALUE_INVALID_DISJUNCTION_ONE_SIDE │ ├── VALUE_INVALID_ELEMENT_LINK_ANOTHER │ ├── VALUE_INVALID_ELEMENT_LINK_SELF │ ├── VALUE_INVALID_ELEMENT_USER │ ├── VALUE_INVALID_ELEMENT_VERSION │ ├── VALUE_VALID_CONJUNCTION_WITH_SPACE │ ├── VALUE_VALID_DISJUNCTION_AND_CONJUNCTION │ ├── VALUE_VALID_DISJUNCTION_WITH_SPACE │ ├── VALUE_VALID_ELEMENT_TYPES │ └── VALUE_VALID_NESTED_MODULE_LINK ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── intellij-plugin ├── .gitignore ├── .run │ ├── Run Plugin.run.xml │ ├── Run Tests.run.xml │ └── Run Verifications.run.xml ├── CHANGELOG.md ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle │ ├── libs.versions.toml │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── qodana.yml ├── settings.gradle.kts └── src │ ├── main │ ├── gen │ │ └── com │ │ │ └── linecorp │ │ │ └── android │ │ │ └── featureflag │ │ │ └── ij │ │ │ └── plugin │ │ │ ├── lexer │ │ │ └── FeatureFlagFlexLexer.java │ │ │ ├── parser │ │ │ └── FeatureFlagParser.java │ │ │ └── psi │ │ │ ├── FeatureFlagEntry.java │ │ │ ├── FeatureFlagModifier.java │ │ │ ├── FeatureFlagProperty.java │ │ │ ├── FeatureFlagTypes.java │ │ │ ├── FeatureFlagValue.java │ │ │ ├── FeatureFlagValueReference.java │ │ │ ├── FeatureFlagVisitor.java │ │ │ └── impl │ │ │ ├── FeatureFlagEntryImpl.java │ │ │ ├── FeatureFlagModifierImpl.java │ │ │ ├── FeatureFlagPropertyImpl.java │ │ │ ├── FeatureFlagValueImpl.java │ │ │ └── FeatureFlagValueReferenceImpl.java │ ├── kotlin │ │ └── com │ │ │ └── linecorp │ │ │ └── android │ │ │ └── featureflag │ │ │ └── ij │ │ │ └── plugin │ │ │ ├── FeatureFlag.bnf │ │ │ ├── FeatureFlag.flex │ │ │ ├── FeatureFlagBundle.kt │ │ │ ├── FeatureFlagFileType.kt │ │ │ ├── FeatureFlagLanguage.kt │ │ │ ├── FeatureFlagUtil.kt │ │ │ ├── commenter │ │ │ └── FeatureFlagCommenter.kt │ │ │ ├── documentation │ │ │ └── FeatureFlagDocumentationProvider.kt │ │ │ ├── gotosymbol │ │ │ └── FeatureFlagChooseByNameContributor.kt │ │ │ ├── icon │ │ │ ├── FeatureFlagIcons.kt │ │ │ └── FeatureFlagPropertyIconProvider.kt │ │ │ ├── lexer │ │ │ └── FeatureFlagLexer.kt │ │ │ ├── linemarker │ │ │ ├── FeatureFlagJavaLineMarkerProvider.kt │ │ │ ├── FeatureFlagKotlinLineMarkerProvider.kt │ │ │ └── FeatureFlagLineMarkerProvider.kt │ │ │ ├── navigationbar │ │ │ └── FeatureFlagStructureAwareNavbar.kt │ │ │ ├── parser │ │ │ └── FeatureFlagParserDefinition.kt │ │ │ ├── psi │ │ │ ├── FeatureFlagElementType.kt │ │ │ ├── FeatureFlagFile.kt │ │ │ ├── FeatureFlagModifierType.kt │ │ │ ├── FeatureFlagNamedElement.kt │ │ │ ├── FeatureFlagTokenSets.kt │ │ │ ├── FeatureFlagTokenType.kt │ │ │ └── impl │ │ │ │ ├── FeatureFlagNamedElementImpl.kt │ │ │ │ └── FeatureFlagPsiImplUtil.kt │ │ │ ├── structureview │ │ │ ├── FeatureFlagFileStructureViewElement.kt │ │ │ ├── FeatureFlagPropertyStructureViewElement.kt │ │ │ ├── FeatureFlagPropertyStructureViewPresentation.kt │ │ │ ├── FeatureFlagStructureViewFactory.kt │ │ │ └── FeatureFlagStructureViewModel.kt │ │ │ └── syntaxhighlight │ │ │ ├── FeatureFlagSyntaxHighlighter.kt │ │ │ └── FeatureFlagSyntaxHighlighterFactory.kt │ └── resources │ │ ├── FeatureFlagIconMappings.json │ │ ├── META-INF │ │ ├── plugin.xml │ │ ├── pluginIcon.svg │ │ └── pluginIcon_dark.svg │ │ ├── icons │ │ ├── expui │ │ │ ├── featureFlagFile.svg │ │ │ ├── featureFlagFile_dark.svg │ │ │ ├── featureFlagSingleGutter.svg │ │ │ ├── featureFlagSingleGutter_dark.svg │ │ │ ├── featureFlagSingleProperty.svg │ │ │ ├── featureFlagSingleProperty_dark.svg │ │ │ ├── featureFlagStructureViewLiteralize.svg │ │ │ ├── featureFlagStructureViewLiteralize_dark.svg │ │ │ ├── featureFlagStructureViewOverridable.svg │ │ │ ├── featureFlagStructureViewOverridable_dark.svg │ │ │ ├── featureFlagStructureViewPrivate.svg │ │ │ └── featureFlagStructureViewPrivate_dark.svg │ │ ├── featureFlagFile.svg │ │ ├── featureFlagFile_dark.svg │ │ ├── featureFlagSingleGutter.svg │ │ ├── featureFlagSingleGutter_dark.svg │ │ ├── featureFlagSingleProperty.svg │ │ ├── featureFlagSingleProperty_dark.svg │ │ ├── featureFlagStructureViewLiteralize.svg │ │ ├── featureFlagStructureViewLiteralize_dark.svg │ │ ├── featureFlagStructureViewOverridable.svg │ │ └── featureFlagStructureViewPrivate.svg │ │ └── messages │ │ └── FeatureFlagBundle.properties │ └── test │ ├── kotlin │ └── com │ │ └── linecorp │ │ └── android │ │ └── featureflag │ │ └── ij │ │ └── plugin │ │ └── FeatureFlagParsingTest.kt │ └── resources │ └── testData │ ├── ParsingTestData.feature_flag │ └── ParsingTestData.txt └── settings.gradle.kts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | max_line_length = 100 10 | tab_width = 4 11 | trim_trailing_whitespace = true 12 | ij_continuation_indent_size = 8 13 | ij_smart_tabs = false 14 | 15 | [{*.kt, *.kts}] 16 | ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL 17 | continuation_indent_size = 4 18 | ij_continuation_indent_size = 4 19 | ij_kotlin_name_count_to_use_star_import = 2147483647 20 | ij_kotlin_name_count_to_use_star_import_for_members = 2147483647 21 | ij_kotlin_imports_layout = *, java., javax., kotlin.*, ^ 22 | ktlint_code_style = intellij_idea 23 | ktlint_standard_trailing-comma-on-call-site = disabled 24 | ktlint_standard_trailing-comma-on-declaration-site = disabled 25 | ktlint_standard_function-signature = disabled 26 | ktlint_standard_multiline-expression-wrapping = disabled 27 | 28 | [*.kts] 29 | max_line_length = 120 30 | 31 | [{*.yml, *.yaml}] 32 | indent_size = 2 33 | ij_yaml_keep_indents_on_empty_lines = false 34 | ij_yaml_keep_line_breaks = true 35 | -------------------------------------------------------------------------------- /.github/workflows/publish-gradle-plugin.yml: -------------------------------------------------------------------------------- 1 | name: Publish Gradle plugin to Gradle Plugins Portal 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | cond_release: 7 | required: true 8 | description: 'Type "release" to release artifacts to Gradle Plugins Portal.' 9 | 10 | jobs: 11 | deploy: 12 | runs-on: ubuntu-latest 13 | environment: deploy 14 | steps: 15 | - name: Fetch Sources 16 | uses: actions/checkout@v4 17 | 18 | - name: Validate Gradle Wrapper 19 | uses: gradle/actions/wrapper-validation@v3 20 | 21 | - name: Setup Java 22 | uses: actions/setup-java@v4 23 | with: 24 | distribution: zulu 25 | java-version: 21 26 | 27 | - name: Setup Gradle 28 | uses: gradle/actions/setup-gradle@v4 29 | 30 | - name: Publish artifacts 31 | if: ${{ github.event.inputs.cond_release == 'release' }} 32 | env: 33 | GRADLE_PUBLISH_KEY: ${{ secrets.GRADLE_PLUGIN_PORTAL_PUBLISH_KEY }} 34 | GRADLE_PUBLISH_SECRET: ${{ secrets.GRADLE_PLUGIN_PORTAL_PUBLISH_SECRET }} 35 | run: ./gradlew clean :feature-flag:publishPlugin 36 | -------------------------------------------------------------------------------- /.github/workflows/publish-intellij-plugin.yml: -------------------------------------------------------------------------------- 1 | name: Publish IntelliJ plugin to Jetbrains Marketplace 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | cond_release: 7 | required: true 8 | description: 'Type "release" to release artifacts to Jetbrains Marketplace.' 9 | 10 | jobs: 11 | deploy: 12 | runs-on: ubuntu-latest 13 | environment: deploy 14 | steps: 15 | - name: Fetch Sources 16 | uses: actions/checkout@v4 17 | 18 | - name: Validate Gradle Wrapper 19 | uses: gradle/actions/wrapper-validation@v3 20 | 21 | - name: Setup Java 22 | uses: actions/setup-java@v4 23 | with: 24 | distribution: zulu 25 | java-version: 17 26 | 27 | - name: Setup Gradle 28 | uses: gradle/actions/setup-gradle@v4 29 | 30 | - name: Publish Plugin 31 | env: 32 | PUBLISH_TOKEN: ${{ secrets.JETBRAINS_MARKETPLACE_PUBLISH_TOKEN }} 33 | CERTIFICATE_CHAIN: ${{ secrets.INTELLIJ_PLUGIN_CERTIFICATE_CHAIN }} 34 | PRIVATE_KEY: ${{ secrets.INTELLIJ_PLUGIN_PRIVATE_KEY }} 35 | PRIVATE_KEY_PASSWORD: ${{ secrets.INTELLIJ_PLUGIN_PRIVATE_KEY_PASSWORD }} 36 | run: | 37 | cd intellij-plugin 38 | ./gradlew :publishPlugin 39 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: Run tests on a pull request 2 | 3 | on: pull_request 4 | 5 | jobs: 6 | detect-changes: 7 | runs-on: ubuntu-latest 8 | permissions: 9 | pull-requests: read 10 | outputs: 11 | gradle-plugin: ${{ steps.filter.outputs.gradle-plugin }} 12 | intellij-plugin: ${{ steps.filter.outputs.intellij-plugin }} 13 | steps: 14 | - uses: dorny/paths-filter@v3 15 | id: filter 16 | with: 17 | filters: | 18 | gradle-plugin: 19 | - '!intellij-plugin/**' 20 | intellij-plugin: 21 | - 'intellij-plugin/**' 22 | - 'qodana.yml' 23 | 24 | build-gradle-plugin: 25 | needs: detect-changes 26 | if: ${{ needs.detect-changes.outputs.gradle-plugin == 'true' }} 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Fetch Sources 30 | uses: actions/checkout@v4 31 | with: 32 | fetch-depth: 0 33 | 34 | - name: Validate Gradle Wrapper 35 | uses: gradle/actions/wrapper-validation@v3 36 | 37 | - name: Setup Java 38 | uses: actions/setup-java@v4 39 | with: 40 | distribution: zulu 41 | java-version: 11 42 | 43 | - name: Setup Gradle 44 | uses: gradle/actions/setup-gradle@v4 45 | 46 | - name: Setup Ruby 47 | uses: ruby/setup-ruby@v1 48 | with: 49 | ruby-version: '3.0' 50 | bundler-cache: true 51 | 52 | - name: Run ktlint and unit test 53 | run: ./gradlew --continue :feature-flag:test :feature-flag:ktlintCheck 54 | 55 | - name: Run Danger 56 | if: ${{ cancelled() != true }} 57 | env: 58 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 59 | JOB_STATUS: ${{ job.status }} 60 | run: bundle exec danger --remove-previous-comments --fail-on-errors=true 61 | 62 | build-intellij-plugin: 63 | needs: detect-changes 64 | if: ${{ needs.detect-changes.outputs.intellij-plugin == 'true' }} 65 | runs-on: ubuntu-latest 66 | steps: 67 | - name: Maximize Build Space 68 | uses: jlumbroso/free-disk-space@main 69 | with: 70 | tool-cache: false 71 | large-packages: false 72 | 73 | - name: Fetch Sources 74 | uses: actions/checkout@v4 75 | 76 | - name: Validate Gradle Wrapper 77 | uses: gradle/actions/wrapper-validation@v3 78 | 79 | - name: Setup Java 80 | uses: actions/setup-java@v4 81 | with: 82 | distribution: zulu 83 | java-version: 17 84 | 85 | - name: Setup Gradle 86 | uses: gradle/actions/setup-gradle@v4 87 | 88 | - name: Setup Plugin Verifier IDEs Cache 89 | uses: actions/cache@v4 90 | with: 91 | path: ~/.pluginVerifier/ides 92 | key: plugin-verifier-${{ hashFiles('intellij-plugin/build/listProductsReleases.txt') }} 93 | 94 | - name: Run Tests 95 | run: | 96 | cd intellij-plugin 97 | ./gradlew :check verifyPlugin -Dplugin.verifier.home.dir=~/.pluginVerifier/ides 98 | 99 | - name: Collect Tests Result 100 | if: ${{ failure() }} 101 | uses: actions/upload-artifact@v4 102 | with: 103 | name: tests-result 104 | path: ${{ github.workspace }}/intellij-plugin/build/reports/tests 105 | 106 | - name: Collect Kover Report 107 | if: ${{ always() }} 108 | uses: actions/upload-artifact@v4 109 | with: 110 | name: kover-result 111 | path: ${{ github.workspace }}/intellij-plugin/build/reports/kover/report.xml 112 | 113 | - name: Collect Plugin Verifier Result 114 | if: ${{ always() }} 115 | uses: actions/upload-artifact@v4 116 | with: 117 | name: pluginVerifier-result 118 | path: ${{ github.workspace }}/intellij-plugin/build/reports/pluginVerifier 119 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/macos,kotlin,android,windows,androidstudio 2 | 3 | ### Kotlin ### 4 | # Compiled class file 5 | 6 | # Log file 7 | 8 | # BlueJ files 9 | *.ctxt 10 | 11 | # Mobile Tools for Java (J2ME) 12 | 13 | # Package Files # 14 | *.jar 15 | *.nar 16 | *.zip 17 | *.tar.gz 18 | *.rar 19 | 20 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 21 | 22 | ### Android ### 23 | # Built application files 24 | *.apk 25 | *.ap_ 26 | 27 | # Files for the ART/Dalvik VM 28 | *.dex 29 | 30 | # Java class files 31 | *.class 32 | 33 | # Generated files 34 | bin/ 35 | gen/ 36 | out/ 37 | 38 | # Gradle files 39 | .gradle/ 40 | build/ 41 | 42 | # Local configuration file (sdk path, etc) 43 | local.properties 44 | 45 | # Proguard folder generated by Eclipse 46 | proguard/ 47 | 48 | # Log Files 49 | *.log 50 | 51 | # Android Studio Navigation editor temp files 52 | .navigation/ 53 | 54 | # Android Studio captures folder 55 | captures/ 56 | 57 | # IntelliJ 58 | *.iml 59 | .idea/ 60 | 61 | # Keystore files 62 | # Uncomment the following line if you do not want to check your keystore files in. 63 | #*.jks 64 | 65 | # External native build folder generated in Android Studio 2.2 and later 66 | .externalNativeBuild 67 | 68 | # Google Services (e.g. APIs or Firebase) 69 | google-services.json 70 | 71 | # Freeline 72 | freeline.py 73 | freeline/ 74 | freeline_project_description.json 75 | 76 | # fastlane 77 | fastlane/report.xml 78 | fastlane/Preview.html 79 | fastlane/screenshots 80 | fastlane/test_output 81 | fastlane/readme.md 82 | 83 | ### Android Patch ### 84 | gen-external-apklibs 85 | 86 | ### AndroidStudio ### 87 | # Covers files to be ignored for android development using Android Studio. 88 | 89 | # Built application files 90 | 91 | # Files for the ART/Dalvik VM 92 | 93 | # Java class files 94 | 95 | # Generated files 96 | 97 | # Gradle files 98 | .gradle 99 | !gradle/wrapper/gradle-wrapper.jar 100 | 101 | # Signing files 102 | .signing/ 103 | 104 | # Local configuration file (sdk path, etc) 105 | 106 | # Proguard folder generated by Eclipse 107 | 108 | # Log Files 109 | 110 | # Android Studio 111 | /*/build/ 112 | /*/local.properties 113 | /*/out 114 | /*/*/build 115 | /*/*/production 116 | *.ipr 117 | *~ 118 | *.swp 119 | 120 | # Android Patch 121 | 122 | # External native build folder generated in Android Studio 2.2 and later 123 | 124 | # NDK 125 | obj/ 126 | 127 | # IntelliJ IDEA 128 | *.iws 129 | /out/ 130 | 131 | # User-specific configurations 132 | .idea/caches/ 133 | .idea/libraries/ 134 | .idea/shelf/ 135 | .idea/.name 136 | .idea/compiler.xml 137 | .idea/copyright/profiles_settings.xml 138 | .idea/encodings.xml 139 | .idea/misc.xml 140 | .idea/modules.xml 141 | .idea/scopes/scope_settings.xml 142 | .idea/vcs.xml 143 | .idea/jsLibraryMappings.xml 144 | .idea/datasources.xml 145 | .idea/dataSources.ids 146 | .idea/sqlDataSources.xml 147 | .idea/dynamic.xml 148 | .idea/uiDesigner.xml 149 | 150 | # OS-specific files 151 | .DS_Store 152 | .DS_Store? 153 | ._* 154 | .Spotlight-V100 155 | .Trashes 156 | ehthumbs.db 157 | Thumbs.db 158 | 159 | # Legacy Eclipse project files 160 | .classpath 161 | .project 162 | .cproject 163 | .settings/ 164 | 165 | # Mobile Tools for Java (J2ME) 166 | .mtj.tmp/ 167 | 168 | # Package Files # 169 | *.war 170 | *.ear 171 | 172 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) 173 | hs_err_pid* 174 | 175 | ## Plugin-specific files: 176 | 177 | # mpeltonen/sbt-idea plugin 178 | .idea_modules/ 179 | 180 | # JIRA plugin 181 | atlassian-ide-plugin.xml 182 | 183 | # Mongo Explorer plugin 184 | .idea/mongoSettings.xml 185 | 186 | # Crashlytics plugin (for Android Studio and IntelliJ) 187 | com_crashlytics_export_strings.xml 188 | crashlytics.properties 189 | crashlytics-build.properties 190 | fabric.properties 191 | 192 | ### AndroidStudio Patch ### 193 | 194 | !/gradle/wrapper/gradle-wrapper.jar 195 | 196 | ### macOS ### 197 | # General 198 | .AppleDouble 199 | .LSOverride 200 | 201 | # Icon must end with two \r 202 | Icon 203 | 204 | # Thumbnails 205 | 206 | # Files that might appear in the root of a volume 207 | .DocumentRevisions-V100 208 | .fseventsd 209 | .TemporaryItems 210 | .VolumeIcon.icns 211 | .com.apple.timemachine.donotpresent 212 | 213 | # Directories potentially created on remote AFP share 214 | .AppleDB 215 | .AppleDesktop 216 | Network Trash Folder 217 | Temporary Items 218 | .apdisk 219 | 220 | ### Windows ### 221 | # Windows thumbnail cache files 222 | ehthumbs_vista.db 223 | 224 | # Dump file 225 | *.stackdump 226 | 227 | # Folder config file 228 | [Dd]esktop.ini 229 | 230 | # Recycle Bin used on file shares 231 | $RECYCLE.BIN/ 232 | 233 | # Windows Installer files 234 | *.cab 235 | *.msi 236 | *.msix 237 | *.msm 238 | *.msp 239 | 240 | # Windows shortcuts 241 | *.lnk 242 | 243 | 244 | # End of https://www.gitignore.io/api/macos,kotlin,android,windows,androidstudio 245 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at dl_oss_dev@linecorp.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute to feature-flag-android 2 | 3 | First of all, thank you so much for taking your time to contribute! feature-flag-android is not very different from any other open source projects. It will 4 | be fantastic if you help us by doing any of the following: 5 | 6 | - File an issue in [the issue tracker](https://github.com/line/feature-flag-android/issues) 7 | to report bugs and propose new features and improvements. 8 | - Ask a question using [the issue tracker](https://github.com/line/feature-flag-android/issues). 9 | - Contribute your work by sending [a pull request](https://github.com/line/feature-flag-android/pulls). 10 | 11 | ## Contributor license agreement 12 | 13 | If you are sending a pull request and it's a non-trivial change beyond fixing 14 | typos, please make sure to sign the [ICLA (Individual Contributor License Agreement)](https://cla-assistant.io/line/feature-flag-android). 15 | Please contact us if you need the CCLA (Corporate Contributor License Agreement). 16 | 17 | ## Code of conduct 18 | 19 | We expect contributors to follow [our code of conduct](./CODE_OF_CONDUCT.md). 20 | -------------------------------------------------------------------------------- /Dangerfile: -------------------------------------------------------------------------------- 1 | github.dismiss_out_of_range_messages({ 2 | error: false, 3 | warning: true, 4 | message: true, 5 | markdown: true 6 | }) 7 | 8 | Dir.glob("feature-flag/build/reports/ktlint/*/ktlint*SourceSetCheck.xml").each { |file| 9 | checkstyle_format.base_path = Dir.pwd 10 | checkstyle_format.report file.to_s 11 | } 12 | 13 | Dir.glob("feature-flag/build/test-results/test/*.xml").each { |file| 14 | junit.parse file 15 | junit.report 16 | } 17 | 18 | return unless status_report[:errors].empty? 19 | 20 | return markdown "Build Failed" if ENV["JOB_STATUS"] != "success" 21 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } 6 | 7 | gem "danger", "8.2.3" 8 | gem "danger-checkstyle_format", "0.1.1" 9 | gem "danger-junit", "1.0.2" 10 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /feature-flag/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask 2 | import org.jlleitschuh.gradle.ktlint.reporter.ReporterType 3 | 4 | plugins { 5 | alias(libs.plugins.kotlin.jvm) 6 | `kotlin-dsl` 7 | alias(libs.plugins.gradle.publish) 8 | `java-gradle-plugin` 9 | alias(libs.plugins.ktlint.gradle) 10 | alias(libs.plugins.ben.manes.gradle) 11 | `maven-publish` 12 | } 13 | 14 | version = libs.versions.feature.flag.get() 15 | group = libs.feature.flag.get().module.group 16 | 17 | gradlePlugin { 18 | website.set("https://github.com/line/feature-flag-android") 19 | vcsUrl.set("https://github.com/line/feature-flag-android.git") 20 | plugins { 21 | register("featureFlagPlugin") { 22 | id = libs.feature.flag.get().module.name 23 | implementationClass = "com.linecorp.android.featureflag.FeatureFlagPlugin" 24 | displayName = "feature-flag-android" 25 | description = "A Gradle plugin to achieve feature flag based development for Android applications." 26 | tags.set(listOf("android", "feature-flag", "feature-toggle")) 27 | } 28 | } 29 | } 30 | 31 | ktlint { 32 | reporters { 33 | reporter(ReporterType.PLAIN) 34 | reporter(ReporterType.CHECKSTYLE) 35 | } 36 | } 37 | 38 | tasks.withType(Test::class.java) { 39 | useJUnitPlatform { 40 | includeEngines("spek2") 41 | } 42 | } 43 | 44 | fun isStable(version: String): Boolean { 45 | val hasStableKeyword = setOf("RELEASE", "FINAL", "GA").any { version.contains(version, ignoreCase = true) } 46 | val regex = """^[0-9,.v-]+(-r)?$""".toRegex() 47 | return hasStableKeyword || regex.matches(version) 48 | } 49 | 50 | tasks.withType { 51 | rejectVersionIf { 52 | !isStable(candidate.version) 53 | } 54 | checkForGradleUpdate = true 55 | } 56 | 57 | dependencies { 58 | compileOnly(libs.android.gradle) 59 | implementation(libs.jsemver) 60 | 61 | testImplementation(libs.kotlin.test) 62 | testImplementation(libs.mockk) 63 | testImplementation(libs.spek2.dsl.jvm) 64 | testRuntimeOnly(libs.kotlin.reflect) 65 | testRuntimeOnly(libs.spek2.runner.junit5) 66 | } 67 | 68 | publishing { 69 | publications { 70 | maybeCreate("pluginMaven", MavenPublication::class.java).apply { 71 | groupId = libs.feature.flag.get().module.group 72 | artifactId = libs.feature.flag.get().module.name 73 | version = libs.versions.feature.flag.get() 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /feature-flag/src/main/kotlin/com/linecorp/android/featureflag/FeatureFlagExtension.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 LINE Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package com.linecorp.android.featureflag 18 | 19 | import com.linecorp.android.featureflag.model.BuildVariant 20 | import org.gradle.api.Project 21 | import org.gradle.api.file.ConfigurableFileCollection 22 | 23 | /** 24 | * A Gradle extension to provide a feature flag file. 25 | * 26 | * **Note**: Keep this an open class because a proxy class is created by the gradle system. 27 | */ 28 | open class FeatureFlagExtension(project: Project) { 29 | val sourceFiles: ConfigurableFileCollection = 30 | project.files(project.rootDir.resolve(DEFAULT_FEATURE_FLAG_PROPERTY_FILE_NAME)) 31 | var phases: Map> = mapOf() 32 | var releasePhaseSet: Set = setOf() 33 | var packageName: String = "" 34 | var versionName: String = "" 35 | 36 | fun buildType(name: String): BuildVariant.Element = BuildVariant.Element.BuildType(name) 37 | 38 | fun flavor(name: String): BuildVariant.Element = BuildVariant.Element.Flavor(name) 39 | 40 | companion object { 41 | private const val DEFAULT_FEATURE_FLAG_PROPERTY_FILE_NAME = "FEATURE_FLAG" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /feature-flag/src/main/kotlin/com/linecorp/android/featureflag/FeatureFlagPlugin.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 LINE Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package com.linecorp.android.featureflag 18 | 19 | import com.android.build.api.variant.ApplicationAndroidComponentsExtension 20 | import com.android.build.api.variant.ApplicationVariant 21 | import com.android.build.api.variant.LibraryAndroidComponentsExtension 22 | import com.android.build.api.variant.LibraryVariant 23 | import com.android.build.api.variant.Variant 24 | import com.android.build.gradle.AppPlugin 25 | import com.android.build.gradle.LibraryPlugin 26 | import com.linecorp.android.featureflag.model.BuildVariant 27 | import com.linecorp.android.featureflag.model.ForciblyOverriddenFeatureFlags 28 | import java.util.Locale 29 | import org.gradle.api.Plugin 30 | import org.gradle.api.Project 31 | import org.gradle.kotlin.dsl.create 32 | import org.gradle.kotlin.dsl.register 33 | 34 | /** 35 | * A gradle plugin adding a task to create a feature flag Java file from a property file. 36 | */ 37 | @Suppress("unused") 38 | class FeatureFlagPlugin : Plugin { 39 | 40 | override fun apply(project: Project) { 41 | val extension = project.extensions.create("featureFlag", project) 42 | 43 | project.plugins.withType(AppPlugin::class.java) { 44 | project.extensions.getByType(ApplicationAndroidComponentsExtension::class.java) 45 | .onVariants { variant -> 46 | installFeatureFlagGenerationTaskForApplication(project, variant, extension) 47 | } 48 | } 49 | project.plugins.withType(LibraryPlugin::class.java) { 50 | project.extensions.getByType(LibraryAndroidComponentsExtension::class.java) 51 | .onVariants { variant -> 52 | installFeatureFlagGenerationTaskForLibrary(project, variant, extension) 53 | } 54 | } 55 | } 56 | 57 | private fun installFeatureFlagGenerationTaskForApplication( 58 | project: Project, 59 | variant: ApplicationVariant, 60 | extension: FeatureFlagExtension 61 | ) { 62 | val versionName = extension.versionName.takeIf(String::isNotEmpty) 63 | ?: variant.outputs.firstNotNullOfOrNull { it.versionName.orNull } 64 | ?: throw RuntimeException( 65 | "Missing `featureFlag.versionName` or `android.defaultConfig.versionName` option" 66 | ) 67 | installFeatureFlagGenerationTask(project, variant, extension, versionName) 68 | } 69 | 70 | private fun installFeatureFlagGenerationTaskForLibrary( 71 | project: Project, 72 | variant: LibraryVariant, 73 | extension: FeatureFlagExtension 74 | ) { 75 | val versionName = extension.versionName.takeIf(String::isNotEmpty) 76 | ?: throw RuntimeException("Missing `featureFlag.versionName` option") 77 | installFeatureFlagGenerationTask(project, variant, extension, versionName) 78 | } 79 | 80 | private fun installFeatureFlagGenerationTask( 81 | project: Project, 82 | variant: Variant, 83 | extension: FeatureFlagExtension, 84 | versionName: String 85 | ) { 86 | val packageNameProvider = extension.packageName.takeIf(String::isNotEmpty) 87 | ?.let { packageName -> project.provider { packageName } } 88 | ?: variant.namespace 89 | ?: throw RuntimeException( 90 | "Missing `featureFlag.packageName` or `android.namespace` option" 91 | ) 92 | 93 | val currentBuildVariant = BuildVariant( 94 | BuildVariant.Element.BuildType(checkNotNull(variant.buildType)), 95 | variant.getProductFlavorSet() 96 | ) 97 | 98 | val capitalizedVariantName = variant.name.replaceFirstChar { 99 | if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() 100 | } 101 | val taskName = "generate${capitalizedVariantName}FeatureFlag" 102 | val taskProvider = project.tasks.register(taskName) { 103 | sourceFiles = extension.sourceFiles 104 | packageName.set(packageNameProvider) 105 | phaseMap = getPhaseMap(extension.phases, currentBuildVariant) 106 | isReleaseVariant = extension.releasePhaseSet.any(currentBuildVariant::includes) 107 | applicationVersionName = versionName 108 | currentUserName = System.getProperty("user.name") 109 | forciblyOverriddenFeatureFlags = ForciblyOverriddenFeatureFlags.parse(project) 110 | } 111 | variant.sources.java 112 | ?.addGeneratedSourceDirectory(taskProvider, FeatureFlagTask::outputDirectory) 113 | } 114 | 115 | internal fun getPhaseMap( 116 | phases: Map>, 117 | currentBuildVariant: BuildVariant 118 | ): Map = phases.mapValues { it.value.any(currentBuildVariant::includes) } 119 | 120 | private fun Variant.getProductFlavorSet(): Set = 121 | productFlavors.map { BuildVariant.Element.Flavor(it.second) }.toSet() 122 | } 123 | -------------------------------------------------------------------------------- /feature-flag/src/main/kotlin/com/linecorp/android/featureflag/loader/FeatureFlagFileTokenizer.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 LINE Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package com.linecorp.android.featureflag.loader 18 | 19 | import com.linecorp.android.featureflag.loader.FeatureFlagFileTokenizer.parse 20 | import com.linecorp.android.featureflag.model.FeatureFlagEntry 21 | 22 | /** 23 | * A string tokenizer for raw string lines of feature flag properties. 24 | * 25 | * Call [parse] with a property line [Sequence] to tokenize the lines. 26 | * Each line must adhere to the following format: 27 | * 28 | * ``` 29 | * [[Option] Name = Value] [# Comment] 30 | * ``` 31 | * 32 | * A line may have two items: a name-value pair (`[Option] Name = Value`) and a comment 33 | * (`# Comment`). 34 | * A blank line is also valid. 35 | * 36 | * Here, a name-value pair consists of the following elements. 37 | * 38 | * - Option: Specifies the flag property such as mutability or visibility. 39 | * - Name: Declares the flag name. 40 | * - Value: Decides the flag boolean value. 41 | */ 42 | internal object FeatureFlagFileTokenizer { 43 | private const val COMMENT_MARKER = "#" 44 | private val FLAG_ENTRY_REGEX = """^(?:([^=]*)\s+)?(\S+)\s*=(.+)$""".toRegex() 45 | 46 | /** 47 | * Extracts tuples of a feature flag name, value, and option from a given raw line sequence. 48 | * 49 | * If the given sequence has a malformed line, this throws [IllegalArgumentException]. 50 | */ 51 | fun parse(lines: Sequence): List = 52 | lines 53 | .map(::trimComment) 54 | .filterNot(String::isBlank) 55 | .map(::parseToRawFeatureFlagEntry) 56 | .toList() 57 | 58 | private fun trimComment(line: String): String = line.split(COMMENT_MARKER)[0] 59 | 60 | private fun parseToRawFeatureFlagEntry(line: String): FeatureFlagEntry { 61 | val groupValues = FLAG_ENTRY_REGEX.find(line)?.groupValues 62 | 63 | require(groupValues != null && groupValues.size == 4) { "Couldn't parse a line: $line" } 64 | check(groupValues[3].isNotBlank()) { "Value mustn't be empty: $line" } 65 | 66 | return FeatureFlagEntry( 67 | name = groupValues[2].trim(), 68 | value = groupValues[3].trim(), 69 | option = groupValues[1].trim() 70 | ) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /feature-flag/src/main/kotlin/com/linecorp/android/featureflag/loader/FeatureFlagOptionParser.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 LINE Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package com.linecorp.android.featureflag.loader 18 | 19 | import com.linecorp.android.featureflag.loader.FeatureFlagOptionParser.parse 20 | import com.linecorp.android.featureflag.model.FeatureFlagOption 21 | import java.util.EnumSet 22 | 23 | /** 24 | * A parser of space-separated feature flag options. 25 | * 26 | * Call [parse] with a raw option string to convert it to a [Set] of [FeatureFlagOption]. 27 | */ 28 | internal object FeatureFlagOptionParser { 29 | 30 | private val OPTION_DELIMITER = """\s+""".toRegex() 31 | internal val OPTION_MAPPING = mapOf( 32 | "OVERRIDABLE" to FeatureFlagOption.OVERRIDABLE, 33 | "PRIVATE" to FeatureFlagOption.PRIVATE, 34 | "LITERALIZE" to FeatureFlagOption.LITERALIZE, 35 | "DEPRECATED" to FeatureFlagOption.DEPRECATED 36 | ) 37 | 38 | /** 39 | * Converts space-separated raw options to a [Set] of [FeatureFlagOption]. 40 | * 41 | * Each raw option should be separated by space characters represented by `\s` of `Regex`. 42 | * Duplicated options are unified. 43 | * If an invalid option string is given, this throws [IllegalArgumentException]. 44 | */ 45 | fun parse(rawOption: String): Set { 46 | val enumList = rawOption 47 | .split(OPTION_DELIMITER) 48 | .filter(String::isNotBlank) 49 | .map(::parseOption) 50 | return if (enumList.isEmpty()) emptySet() else EnumSet.copyOf(enumList) 51 | } 52 | 53 | private fun parseOption(rawOption: String): FeatureFlagOption = 54 | requireNotNull(OPTION_MAPPING[rawOption]) { "A specified option is undefined: $rawOption" } 55 | } 56 | -------------------------------------------------------------------------------- /feature-flag/src/main/kotlin/com/linecorp/android/featureflag/loader/FeatureFlagSelectorEvaluator.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 LINE Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package com.linecorp.android.featureflag.loader 18 | 19 | import com.github.zafarkhaja.semver.Version 20 | import com.linecorp.android.featureflag.loader.FeatureFlagSelectorEvaluator.evaluate 21 | import com.linecorp.android.featureflag.model.BuildEnvironment 22 | import com.linecorp.android.featureflag.model.DisjunctionNormalForm.Conjunction 23 | import com.linecorp.android.featureflag.model.DisjunctionNormalForm.Disjunction 24 | import com.linecorp.android.featureflag.model.FeatureFlagAppliedElement 25 | import com.linecorp.android.featureflag.model.FeatureFlagAppliedElement.Constant 26 | import com.linecorp.android.featureflag.model.FeatureFlagAppliedElement.Variable 27 | import com.linecorp.android.featureflag.model.FeatureFlagElement 28 | 29 | /** 30 | * An evaluator of [FeatureFlagElement]s in [Disjunction]. 31 | * 32 | * [evaluate] converts flag elements to a boolean value (except for [FeatureFlagElement.Link]) with 33 | * applying the current build environment. 34 | * The evaluation result keeps the same disjunctive normal form as the given structure. 35 | * 36 | * Here, a converted element is classified as one of the following two types. 37 | * 38 | * - Constant: Represents a boolean constant value. This is the evaluation result of 39 | * [FeatureFlagElement.User], [FeatureFlagElement.Version], or [Element.Phase]. 40 | * - Link: Represents a link to another flag. If the module name is empty, it is considered that the 41 | * linked flag is in the same module. 42 | * 43 | * For example, [FeatureFlagSelectorEvaluator.evaluate] converts 44 | * `Disjunction(Conjunction(Phase("RELEASE"), Version("1.0.0"), Link("", "ANOTHER_FLAG")), Conjunction(User("USER")))` 45 | * to 46 | * `Disjunction(Conjunction(Constant(true), Constant(true), Link("", "ANOTHER_FLAG")), Constant(false))` 47 | * if the current phase is `RELEASE`, application version is `1.1.0`, and executor user name is 48 | * `USER_A`. 49 | */ 50 | internal object FeatureFlagSelectorEvaluator { 51 | 52 | /** 53 | * Converts [FeatureFlagElement]s in [value] to [FeatureFlagAppliedElement]s with applying 54 | * [buildEnviroment]. 55 | * 56 | * If the given value has undefined `Phase` in `buildEnvironment`, this throws 57 | * [IllegalArgumentException]. 58 | */ 59 | fun evaluate( 60 | value: Disjunction, 61 | buildEnvironment: BuildEnvironment 62 | ): Disjunction { 63 | val evaluatedConjunctions = value.values 64 | .map { evaluateConjunction(it, buildEnvironment) } 65 | return Disjunction(evaluatedConjunctions) 66 | } 67 | 68 | private fun evaluateConjunction( 69 | selectorGroup: Conjunction, 70 | buildEnvironment: BuildEnvironment 71 | ): Conjunction { 72 | val evaluatedElements = selectorGroup.values 73 | .map { evaluateElement(it, buildEnvironment) } 74 | return Conjunction(evaluatedElements) 75 | } 76 | 77 | private fun evaluateElement( 78 | element: FeatureFlagElement, 79 | buildEnvironment: BuildEnvironment 80 | ): FeatureFlagAppliedElement = when (element) { 81 | is FeatureFlagElement.Phase -> Constant(isEnabledPhase(element, buildEnvironment.phasesMap)) 82 | is FeatureFlagElement.User -> Constant(isEnabledUser(element, buildEnvironment.userName)) 83 | is FeatureFlagElement.Link -> Variable(element.link) 84 | is FeatureFlagElement.Version -> Constant( 85 | isEnabledVersion(element, buildEnvironment.applicationVersion) 86 | ) 87 | } 88 | 89 | private fun isEnabledPhase( 90 | element: FeatureFlagElement.Phase, 91 | phasesMap: Map 92 | ): Boolean = requireNotNull(phasesMap[element.phase]) { "Unknown phase: ${element.phase}" } 93 | 94 | private fun isEnabledUser( 95 | element: FeatureFlagElement.User, 96 | userName: String 97 | ): Boolean = element.name == userName 98 | 99 | private fun isEnabledVersion( 100 | element: FeatureFlagElement.Version, 101 | applicationVersion: Version 102 | ): Boolean = Version.valueOf(element.version) <= applicationVersion 103 | } 104 | -------------------------------------------------------------------------------- /feature-flag/src/main/kotlin/com/linecorp/android/featureflag/loader/FeatureFlagValueOptimizer.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 LINE Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package com.linecorp.android.featureflag.loader 18 | 19 | import com.linecorp.android.featureflag.model.DisjunctionNormalForm.Conjunction 20 | import com.linecorp.android.featureflag.model.DisjunctionNormalForm.Disjunction 21 | import com.linecorp.android.featureflag.model.FeatureFlagData 22 | import com.linecorp.android.featureflag.model.FlagLink 23 | import com.linecorp.android.featureflag.model.FeatureFlagAppliedElement as AppliedElement 24 | 25 | /** 26 | * An optimizer of [AppliedElement]s in the disjunctive normal form. 27 | * 28 | * This calculates elements with logical operation of disjunction and conjunction. The result becomes 29 | * a single boolean value or the disjunctive normal form of [FlagLink]s. 30 | * 31 | * For example, [FeatureFlagSelectorEvaluator.evaluate] converts 32 | * `Disjunction(Conjunction(Constant(true), Constant(true), Link("", "ANOTHER_FLAG")), Conjunction(Constant(false)))` 33 | * to 34 | * `Disjunction(Conjunction(Link("", "ANOTHER_FLAG")))` 35 | */ 36 | internal object FeatureFlagValueOptimizer { 37 | 38 | fun optimize(disjunction: Disjunction): FeatureFlagData.Value { 39 | val optimizedDisjunction = disjunction.values.map { it.values.calculateConjunction() } 40 | return optimizedDisjunction.calculateDisjunction() 41 | } 42 | 43 | private fun List.calculateDisjunction(): FeatureFlagData.Value = 44 | when { 45 | any { it == ConjunctionCalculationResult.True } -> FeatureFlagData.Value.True 46 | all { it == ConjunctionCalculationResult.False } -> FeatureFlagData.Value.False 47 | else -> { 48 | val links = filterIsInstance() 49 | .map(ConjunctionCalculationResult.Links::links) 50 | FeatureFlagData.Value.Links(Disjunction(links)) 51 | } 52 | } 53 | 54 | private fun List.calculateConjunction(): ConjunctionCalculationResult = when { 55 | all { it == AppliedElement.Constant(true) } -> ConjunctionCalculationResult.True 56 | any { it == AppliedElement.Constant(false) } -> ConjunctionCalculationResult.False 57 | else -> { 58 | val links = 59 | filterIsInstance().map(AppliedElement.Variable::link) 60 | ConjunctionCalculationResult.Links(Conjunction(links)) 61 | } 62 | } 63 | 64 | private sealed class ConjunctionCalculationResult { 65 | object True : ConjunctionCalculationResult() 66 | object False : ConjunctionCalculationResult() 67 | class Links(val links: Conjunction) : ConjunctionCalculationResult() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /feature-flag/src/main/kotlin/com/linecorp/android/featureflag/model/BuildEnvironment.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 LINE Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package com.linecorp.android.featureflag.model 18 | 19 | import com.github.zafarkhaja.semver.Version 20 | 21 | /** 22 | * A model class of the current build information: build phase, version, and user. 23 | */ 24 | internal class BuildEnvironment( 25 | /** 26 | * A map from the build phase strings defined in the build script to boolean values. 27 | * 28 | * The result of `phasesMap[phaseString]` is one of the followings. 29 | * - true: Flags with the given phase are enabled. 30 | * - false: Flags with the given phase are disabled. 31 | * - null: The given phase is undefined in the build script. 32 | */ 33 | val phasesMap: Map, 34 | /** 35 | * A version of this module defined in the build script. 36 | */ 37 | val applicationVersion: Version, 38 | /** 39 | * An account name of the current task executor. 40 | */ 41 | val userName: String 42 | ) 43 | -------------------------------------------------------------------------------- /feature-flag/src/main/kotlin/com/linecorp/android/featureflag/model/BuildVariant.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 LINE Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package com.linecorp.android.featureflag.model 18 | 19 | /** 20 | * A data class represents a build variant, that is, a pair of build type and flavors. 21 | * 22 | * This class must be public because an extension proxy class generated by Gradle requires this 23 | * class. 24 | */ 25 | data class BuildVariant( 26 | val buildType: Element.BuildType, 27 | val flavors: Set 28 | ) { 29 | internal fun includes(variant: Element): Boolean = when (variant) { 30 | is Element.BuildType -> variant == buildType 31 | is Element.Flavor -> flavors.contains(variant) 32 | } 33 | 34 | sealed class Element { 35 | data class BuildType(val buildTypeName: String) : Element() 36 | data class Flavor(val productFlavorName: String) : Element() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /feature-flag/src/main/kotlin/com/linecorp/android/featureflag/model/DisjunctionNormalForm.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 LINE Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package com.linecorp.android.featureflag.model 18 | 19 | /** 20 | * A structure of the disjunctive normal form of [T]. 21 | * A form instance is represented by `Disjunction(Conjunction(T, ...), ...)` 22 | */ 23 | internal object DisjunctionNormalForm { 24 | /** 25 | * A model of disjunction of [values], where each value is conjunction of [T]. 26 | */ 27 | internal class Disjunction(val values: List>) 28 | 29 | /** 30 | * A data model represents conjunction of [values]. 31 | */ 32 | internal class Conjunction(val values: List) 33 | } 34 | -------------------------------------------------------------------------------- /feature-flag/src/main/kotlin/com/linecorp/android/featureflag/model/FeatureFlagAppliedElement.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 LINE Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package com.linecorp.android.featureflag.model 18 | 19 | /** 20 | * A data model of feature flag element with applying the current build environment information. 21 | * As the result, elements except for [Variable] are represented by a simple boolean type, [Constant]. 22 | */ 23 | internal sealed class FeatureFlagAppliedElement { 24 | /** 25 | * An element to specify a constant boolean value. Converted from [FeatureFlagElement.Phase], 26 | * [FeatureFlagElement.User], and [FeatureFlagElement.Version] with current build environment information. 27 | */ 28 | data class Constant(val value: Boolean) : FeatureFlagAppliedElement() 29 | 30 | /** 31 | * An element to specify another flag to delegate this flag enability. 32 | */ 33 | data class Variable(val link: FlagLink) : FeatureFlagAppliedElement() 34 | } 35 | -------------------------------------------------------------------------------- /feature-flag/src/main/kotlin/com/linecorp/android/featureflag/model/FeatureFlagData.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 LINE Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package com.linecorp.android.featureflag.model 18 | 19 | /** 20 | * Flag properties of a feature in a specific phase. 21 | * Mainly, this consists of the flag name and value representing if the flag is enabled. 22 | */ 23 | internal data class FeatureFlagData( 24 | val name: String, 25 | val value: Value, 26 | val options: Set 27 | ) { 28 | /** 29 | * A value of this flag entry. The value is a single boolean value or the disjunctive normal form of [FlagLink]s. 30 | */ 31 | sealed class Value { 32 | /** 33 | * A value to enable the flag. 34 | */ 35 | object True : Value() 36 | 37 | /** 38 | * A value to disable the flag. 39 | */ 40 | object False : Value() 41 | 42 | /** 43 | * A value decided by other flags. 44 | * The flags are represented with with the disjunctive normal form of [FlagLink]s. 45 | */ 46 | data class Links( 47 | val linksDisjunction: DisjunctionNormalForm.Disjunction 48 | ) : Value() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /feature-flag/src/main/kotlin/com/linecorp/android/featureflag/model/FeatureFlagElement.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 LINE Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package com.linecorp.android.featureflag.model 18 | 19 | /** 20 | * A data model to specify a condition to enable a feature flag. 21 | */ 22 | internal sealed class FeatureFlagElement { 23 | /** 24 | * An element to specify a build phase set to enable the flag. 25 | */ 26 | data class Phase(val phase: String) : FeatureFlagElement() 27 | 28 | /** 29 | * An element to specify a user name of the current task executor to enable the flag. 30 | */ 31 | data class User(val name: String) : FeatureFlagElement() 32 | 33 | /** 34 | * An element to specify another flag to delegate this flag enability. 35 | */ 36 | data class Link(val link: FlagLink) : FeatureFlagElement() 37 | 38 | /** 39 | * An element to specify a version lower bound to enable the flag. 40 | */ 41 | data class Version(val version: String) : FeatureFlagElement() 42 | } 43 | -------------------------------------------------------------------------------- /feature-flag/src/main/kotlin/com/linecorp/android/featureflag/model/FeatureFlagEntry.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 LINE Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package com.linecorp.android.featureflag.model 18 | 19 | /** 20 | * A entry of feature flag which is a tuple of raw strings: a feature name string, value string, 21 | * and option string. 22 | */ 23 | internal data class FeatureFlagEntry(val name: String, val value: String, val option: String) 24 | -------------------------------------------------------------------------------- /feature-flag/src/main/kotlin/com/linecorp/android/featureflag/model/FeatureFlagOption.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 LINE Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package com.linecorp.android.featureflag.model 18 | 19 | /** 20 | * Type of feature flag options to specify the flag visibility, mutability and annotations. 21 | */ 22 | internal enum class FeatureFlagOption { 23 | /** 24 | * An option to limit access scope of a flag. 25 | * Other flags can access to a flag with this option only when they are declared in the same 26 | * module. 27 | */ 28 | PRIVATE, 29 | 30 | /** 31 | * An option to make a flag modifiable on runtime. 32 | * This option cannot be used with [LITERALIZE] option at the same time. 33 | */ 34 | OVERRIDABLE, 35 | 36 | /** 37 | * An option to allow a flag value to use a boolean literal even in non-release phase. 38 | * Flags with this option does not wrap the value by dynamic instantiation. 39 | * Then, the flags won't suppress static analyser warnings. 40 | * This option cannot be used with [OVERRIDABLE] option at the same time. 41 | */ 42 | LITERALIZE, 43 | 44 | /** 45 | * An option to annotate a flag with [Deprecated]. 46 | */ 47 | DEPRECATED, 48 | } 49 | -------------------------------------------------------------------------------- /feature-flag/src/main/kotlin/com/linecorp/android/featureflag/model/FlagLink.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 LINE Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package com.linecorp.android.featureflag.model 18 | 19 | /** 20 | * A flag value element to refer to another flag value to decide enabled or not. 21 | * 22 | * If a flag has a link with empty [packageName], it means the linked and linking flags are in the 23 | * same module. 24 | */ 25 | internal data class FlagLink(val packageName: String, val flagName: String) 26 | -------------------------------------------------------------------------------- /feature-flag/src/main/kotlin/com/linecorp/android/featureflag/model/ForciblyOverriddenFeatureFlags.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 LINE Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package com.linecorp.android.featureflag.model 18 | 19 | import org.gradle.api.Project 20 | 21 | /** 22 | * Sets of feature flags to be turned on or off forcibly. 23 | * 24 | * Call [ForciblyOverriddenFeatureFlags.Companion.parse] to create this value from a gradle property 25 | * options. 26 | */ 27 | internal data class ForciblyOverriddenFeatureFlags( 28 | val forciblyEnabledFlags: Set, 29 | val forciblyDisabledFlags: Set 30 | ) { 31 | companion object { 32 | private const val PARAMETER_FORCE_ENABLE = "featureFlagOn" 33 | private const val PARAMETER_FORCE_DISABLE = "featureFlagOff" 34 | 35 | /** 36 | * Parses custom feature flag options from gradle property. 37 | * (ex: ./gradlew :task -PfeatureFlagOn=FEATURE_AAA,FEATURE_BBB) 38 | */ 39 | fun parse(project: Project): ForciblyOverriddenFeatureFlags = 40 | ForciblyOverriddenFeatureFlags( 41 | project.parseFeatureFlagInBuildParameter(PARAMETER_FORCE_ENABLE), 42 | project.parseFeatureFlagInBuildParameter(PARAMETER_FORCE_DISABLE) 43 | ) 44 | 45 | private fun Project.parseFeatureFlagInBuildParameter(parameterName: String): Set = 46 | properties[parameterName]?.toString()?.split(',')?.toSet() ?: emptySet() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /feature-flag/src/main/kotlin/com/linecorp/android/featureflag/util/Extensions.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 LINE Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package com.linecorp.android.featureflag.util 18 | 19 | import java.io.File 20 | 21 | internal fun File.resolvePackageName(packageName: String): File { 22 | val packagePath = packageName.replace('.', File.separatorChar) 23 | return resolve(packagePath) 24 | } 25 | -------------------------------------------------------------------------------- /feature-flag/src/test/kotlin/com/linecorp/android/featureflag/FeatureFlagExtensionTest.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 LINE Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package com.linecorp.android.featureflag 18 | 19 | import com.linecorp.android.featureflag.model.BuildVariant 20 | import io.mockk.every 21 | import io.mockk.mockk 22 | import java.io.File 23 | import kotlin.test.assertEquals 24 | import org.gradle.api.Project 25 | import org.gradle.api.file.ConfigurableFileCollection 26 | import org.spekframework.spek2.Spek 27 | import org.spekframework.spek2.style.specification.describe 28 | 29 | object FeatureFlagExtensionTest : Spek({ 30 | describe("public functions return correct values") { 31 | val mockedSourceFiles = mockk() 32 | val project: Project = mockk { 33 | every { rootDir } returns File("/tmp/") 34 | every { files(File("/tmp/FEATURE_FLAG")) } returns mockedSourceFiles 35 | } 36 | val extension by memoized { 37 | FeatureFlagExtension( 38 | project 39 | ) 40 | } 41 | 42 | it("buildType") { 43 | assertEquals( 44 | BuildVariant.Element.BuildType("release"), 45 | extension.buildType("release") 46 | ) 47 | } 48 | it("flavor") { 49 | assertEquals( 50 | BuildVariant.Element.Flavor("production"), 51 | extension.flavor("production") 52 | ) 53 | } 54 | } 55 | }) 56 | -------------------------------------------------------------------------------- /feature-flag/src/test/kotlin/com/linecorp/android/featureflag/loader/FeatureFlagFileTokenizerTest.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 LINE Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package com.linecorp.android.featureflag.loader 18 | 19 | import com.linecorp.android.featureflag.model.FeatureFlagEntry 20 | import com.linecorp.android.featureflag.utils.assertFailureMessage 21 | import java.io.File 22 | import kotlin.test.assertEquals 23 | import org.spekframework.spek2.Spek 24 | import org.spekframework.spek2.style.specification.describe 25 | 26 | /** 27 | * Tests for [FeatureFlagFileTokenizer]. 28 | * All the text resources are in "tests/FeatureFlagFileTokenizerTest/" directory. 29 | */ 30 | object FeatureFlagFileTokenizerTest : Spek({ 31 | 32 | fun loadSequenceFromFile(name: String): Sequence { 33 | val url = checkNotNull( 34 | javaClass.classLoader.getResource("tests/FeatureFlagFileTokenizerTest/$name") 35 | ) 36 | return File(url.toURI()).bufferedReader().lineSequence() 37 | } 38 | 39 | describe("Parsed result is correct") { 40 | it("with options") { 41 | assertEquals( 42 | listOf( 43 | FeatureFlagEntry("FLAG_1", "VALUE", "OPTION"), 44 | FeatureFlagEntry("FLAG_2", "VALUE", "OPTION"), 45 | FeatureFlagEntry("FLAG_3", "VALUE", "OPTION1 OPTION2"), 46 | FeatureFlagEntry("FLAG_4", "VALUE", "OPTION1 OPTION2") 47 | ), 48 | FeatureFlagFileTokenizer.parse(loadSequenceFromFile("FLAG_VALID_OPTION")) 49 | ) 50 | } 51 | it("with name") { 52 | assertEquals( 53 | listOf( 54 | FeatureFlagEntry("FLAG_1", "VALUE", ""), 55 | FeatureFlagEntry("FLAG_2", "VALUE", ""), 56 | FeatureFlagEntry("FLAG_3", "VALUE", "") 57 | ), 58 | FeatureFlagFileTokenizer.parse(loadSequenceFromFile("FLAG_VALID_NAME")) 59 | ) 60 | } 61 | it("with value") { 62 | assertEquals( 63 | listOf( 64 | FeatureFlagEntry("FLAG_1", "VALUE", ""), 65 | FeatureFlagEntry("FLAG_2", "VALUE", ""), 66 | FeatureFlagEntry("FLAG_3", "VALUE VALUE", ""), 67 | FeatureFlagEntry("FLAG_4", "VALUE", ""), 68 | FeatureFlagEntry("FLAG_5", "VALUE=VALUE", "") 69 | ), 70 | FeatureFlagFileTokenizer.parse(loadSequenceFromFile("FLAG_VALID_VALUE")) 71 | ) 72 | } 73 | it("with empty") { 74 | assertEquals( 75 | emptyList(), 76 | FeatureFlagFileTokenizer.parse(loadSequenceFromFile("FLAG_VALID_EMPTY")) 77 | ) 78 | } 79 | } 80 | 81 | describe("Parsing is failed") { 82 | it("if a line has't key-value pair") { 83 | assertFailureMessage("Couldn't parse a line: INVALID_LINE") { 84 | FeatureFlagFileTokenizer.parse(loadSequenceFromFile("FLAG_INVALID_NO_KEY_VALUE")) 85 | } 86 | } 87 | it("if a key is empty") { 88 | assertFailureMessage("Couldn't parse a line: =VALUE") { 89 | FeatureFlagFileTokenizer.parse(loadSequenceFromFile("FLAG_INVALID_EMPTY_KEY")) 90 | } 91 | } 92 | it("if a key is blank") { 93 | assertFailureMessage("Couldn't parse a line: =VALUE") { 94 | FeatureFlagFileTokenizer.parse(loadSequenceFromFile("FLAG_INVALID_BLANK_KEY")) 95 | } 96 | } 97 | it("if a value is empty") { 98 | assertFailureMessage("Couldn't parse a line: KEY=") { 99 | FeatureFlagFileTokenizer.parse(loadSequenceFromFile("FLAG_INVALID_EMPTY_VALUE")) 100 | } 101 | } 102 | it("if a value is blank") { 103 | assertFailureMessage("Value mustn't be empty: KEY= ") { 104 | FeatureFlagFileTokenizer.parse(loadSequenceFromFile("FLAG_INVALID_BLANK_VALUE")) 105 | } 106 | } 107 | } 108 | }) 109 | -------------------------------------------------------------------------------- /feature-flag/src/test/kotlin/com/linecorp/android/featureflag/loader/FeatureFlagOptionParserTest.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 LINE Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package com.linecorp.android.featureflag.loader 18 | 19 | import com.linecorp.android.featureflag.model.FeatureFlagOption 20 | import com.linecorp.android.featureflag.model.FeatureFlagOption.DEPRECATED 21 | import com.linecorp.android.featureflag.model.FeatureFlagOption.OVERRIDABLE 22 | import com.linecorp.android.featureflag.model.FeatureFlagOption.PRIVATE 23 | import com.linecorp.android.featureflag.utils.assertFailureMessage 24 | import java.io.File 25 | import kotlin.test.assertEquals 26 | import org.spekframework.spek2.Spek 27 | import org.spekframework.spek2.style.specification.describe 28 | 29 | /** 30 | * Tests for [FeatureFlagOptionParser]. 31 | * All the text resources are in "tests/FeatureFlagOptionParser/" directory. 32 | */ 33 | object FeatureFlagOptionParserTest : Spek({ 34 | fun loadLinesFromFile(name: String): List { 35 | val url = checkNotNull( 36 | javaClass.classLoader.getResource("tests/FeatureFlagOptionParserTest/$name") 37 | ) 38 | return File(url.toURI()).bufferedReader().lineSequence().toList() 39 | } 40 | 41 | describe("Precondition") { 42 | it("A reverse map of Option enum is sufficient") { 43 | val actualEnums = FeatureFlagOption.values().toList() 44 | val reverseMapContainsEnums = FeatureFlagOptionParser.OPTION_MAPPING.values 45 | 46 | assertEquals(actualEnums.size, reverseMapContainsEnums.size) 47 | assertEquals(0, reverseMapContainsEnums.subtract(actualEnums).size) 48 | assertEquals(0, actualEnums.subtract(reverseMapContainsEnums).size) 49 | } 50 | } 51 | 52 | describe("Parsed result is correct") { 53 | context("with normal case") { 54 | val expectedResults = listOf( 55 | setOf(PRIVATE), 56 | setOf(PRIVATE, OVERRIDABLE), 57 | setOf(DEPRECATED), 58 | setOf(PRIVATE, DEPRECATED), 59 | setOf(PRIVATE, OVERRIDABLE, DEPRECATED) 60 | ) 61 | val testCases = loadLinesFromFile("OPTION_VALID_NORMAL") 62 | 63 | testCases.forEachIndexed { index, testValue -> 64 | it(testValue) { 65 | assertEquals(expectedResults[index], FeatureFlagOptionParser.parse(testValue)) 66 | } 67 | } 68 | } 69 | 70 | context("with empty") { 71 | it("Empty option") { 72 | assertEquals(emptySet(), FeatureFlagOptionParser.parse("")) 73 | } 74 | } 75 | 76 | context("with duplicated option") { 77 | val expectedResults = listOf( 78 | setOf(PRIVATE), 79 | setOf(PRIVATE, OVERRIDABLE), 80 | setOf(PRIVATE, OVERRIDABLE), 81 | setOf(DEPRECATED), 82 | setOf(DEPRECATED, PRIVATE), 83 | setOf(PRIVATE, OVERRIDABLE, DEPRECATED), 84 | setOf(PRIVATE, OVERRIDABLE, DEPRECATED) 85 | ) 86 | 87 | val testCases = loadLinesFromFile("OPTION_VALID_DUPLICATED") 88 | 89 | testCases.forEachIndexed { index, testValue -> 90 | it(testValue) { 91 | assertEquals(expectedResults[index], FeatureFlagOptionParser.parse(testValue)) 92 | } 93 | } 94 | } 95 | 96 | context("with extra spaces") { 97 | val expectedResults = listOf( 98 | setOf(PRIVATE), 99 | setOf(PRIVATE), 100 | setOf(PRIVATE, OVERRIDABLE), 101 | setOf(PRIVATE, OVERRIDABLE), 102 | setOf(PRIVATE, OVERRIDABLE) 103 | ) 104 | 105 | val testCases = loadLinesFromFile("OPTION_VALID_SPACE") 106 | 107 | testCases.forEachIndexed { index, testValue -> 108 | it(""""$testValue"""") { 109 | assertEquals(expectedResults[index], FeatureFlagOptionParser.parse(testValue)) 110 | } 111 | } 112 | } 113 | } 114 | 115 | describe("Parsing is failed") { 116 | it("with undefined option") { 117 | assertFailureMessage( 118 | "A specified option is undefined: INVALID" 119 | ) { 120 | FeatureFlagOptionParser.parse( 121 | loadLinesFromFile("OPTION_INVALID_UNDEFINED_OPTION")[0] 122 | ) 123 | } 124 | } 125 | } 126 | }) 127 | -------------------------------------------------------------------------------- /feature-flag/src/test/kotlin/com/linecorp/android/featureflag/utils/AssertUtils.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 LINE Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package com.linecorp.android.featureflag.utils 18 | 19 | import com.linecorp.android.featureflag.model.DisjunctionNormalForm 20 | import kotlin.test.assertEquals 21 | import kotlin.test.assertFailsWith 22 | 23 | /** 24 | * Asserts that a [block] operation fails with an exception, where the type is [T] and the message 25 | * is [failureMessage]. 26 | */ 27 | internal inline fun assertFailureMessage( 28 | failureMessage: String, 29 | block: () -> Unit 30 | ) { 31 | val exception = assertFailsWith { block() } 32 | assertEquals(failureMessage, exception.message) 33 | } 34 | 35 | /** 36 | * Asserts that the given two [DisjunctionNormalForm.Disjunction]s are equal. 37 | * In other words, returns true if and only if equals returns true for each conjunction pair in 38 | * zipped disjunctions. 39 | */ 40 | internal fun assertDisjunction( 41 | expected: DisjunctionNormalForm.Disjunction, 42 | actual: DisjunctionNormalForm.Disjunction 43 | ) { 44 | assertEquals(expected.values.size, actual.values.size, "Disjunction sizes are different: ") 45 | expected.values.zip(actual.values).forEach { (expectedConjunction, actualConjunction) -> 46 | assertEquals(expectedConjunction.values, actualConjunction.values) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /feature-flag/src/test/kotlin/com/linecorp/android/featureflag/utils/TestUtils.kt: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 LINE Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package com.linecorp.android.featureflag.utils 18 | 19 | import com.linecorp.android.featureflag.model.DisjunctionNormalForm 20 | 21 | internal fun disjunctionOf( 22 | vararg conjunctions: DisjunctionNormalForm.Conjunction 23 | ): DisjunctionNormalForm.Disjunction = DisjunctionNormalForm.Disjunction(conjunctions.toList()) 24 | 25 | internal fun conjunctionOf(vararg elements: T): DisjunctionNormalForm.Conjunction = 26 | DisjunctionNormalForm.Conjunction(elements.toList()) 27 | -------------------------------------------------------------------------------- /feature-flag/src/test/resources/tests/FeatureFlagFileTokenizerTest/FLAG_INVALID_BLANK_KEY: -------------------------------------------------------------------------------- 1 | # THE FOLLOWING LINE HAS A SPACE AS A KEY. 2 | =VALUE 3 | -------------------------------------------------------------------------------- /feature-flag/src/test/resources/tests/FeatureFlagFileTokenizerTest/FLAG_INVALID_BLANK_VALUE: -------------------------------------------------------------------------------- 1 | # THE FOLLOWING LINE HAS A SPACE AS A VALUE. 2 | KEY= 3 | -------------------------------------------------------------------------------- /feature-flag/src/test/resources/tests/FeatureFlagFileTokenizerTest/FLAG_INVALID_EMPTY_KEY: -------------------------------------------------------------------------------- 1 | # THE FOLLOWING LINE HAS NO KEY. 2 | =VALUE 3 | -------------------------------------------------------------------------------- /feature-flag/src/test/resources/tests/FeatureFlagFileTokenizerTest/FLAG_INVALID_EMPTY_VALUE: -------------------------------------------------------------------------------- 1 | # THE FOLLOWING LINE HAS NO VALUE. 2 | KEY= 3 | -------------------------------------------------------------------------------- /feature-flag/src/test/resources/tests/FeatureFlagFileTokenizerTest/FLAG_INVALID_NO_KEY_VALUE: -------------------------------------------------------------------------------- 1 | # THE FOLLOWING LINE ISN'T A KEY-VALUE PAIR 2 | INVALID_LINE 3 | -------------------------------------------------------------------------------- /feature-flag/src/test/resources/tests/FeatureFlagFileTokenizerTest/FLAG_VALID_EMPTY: -------------------------------------------------------------------------------- 1 | ### THIS IS COMMENT LINE ### 2 | # THE FOLLOWING LINE IS EMPTY 3 | 4 | # THE FOLLOWING LINE HAS A SPACE 5 | 6 | # THIS LINE HAS A SPACE WITH A COMMENT 7 | 8 | -------------------------------------------------------------------------------- /feature-flag/src/test/resources/tests/FeatureFlagFileTokenizerTest/FLAG_VALID_NAME: -------------------------------------------------------------------------------- 1 | FLAG_1 = VALUE # expected option: "", name: "FLAG_1", value: "VALUE" 2 | FLAG_2= VALUE # expected option: "", name: "FLAG_2", value: "VALUE" 3 | FLAG_3 = VALUE # expected option: "", name: "FLAG_3", value: "VALUE" 4 | -------------------------------------------------------------------------------- /feature-flag/src/test/resources/tests/FeatureFlagFileTokenizerTest/FLAG_VALID_OPTION: -------------------------------------------------------------------------------- 1 | OPTION FLAG_1 = VALUE # expected option: "OPTION", name: "FLAG_1", value: "VALUE" 2 | OPTION FLAG_2 = VALUE # expected option: "OPTION", name: "FLAG_2", value: "VALUE" 3 | OPTION1 OPTION2 FLAG_3 = VALUE # expected option: "OPTION1 OPTION2", name: "FLAG_3", value: "VALUE" 4 | OPTION1 OPTION2 FLAG_4 = VALUE # expected option: "OPTION1 OPTION2", name: "FLAG_4", value: "VALUE" 5 | -------------------------------------------------------------------------------- /feature-flag/src/test/resources/tests/FeatureFlagFileTokenizerTest/FLAG_VALID_VALUE: -------------------------------------------------------------------------------- 1 | FLAG_1 =VALUE # expected option: "", name: "FLAG_1", value: "VALUE" 2 | FLAG_2 = VALUE # expected option: "", name: "FLAG_2", value: "VALUE" 3 | FLAG_3 = VALUE VALUE # expected option: "", name: "FLAG_3", value: "VALUE VALUE" 4 | FLAG_4 = VALUE#VALUE # expected option: "", name: "FLAG_4", value: "VALUE" 5 | FLAG_5 = VALUE=VALUE # expected option: "", name: "FLAG_5", value: "VALUE=VALUE" 6 | -------------------------------------------------------------------------------- /feature-flag/src/test/resources/tests/FeatureFlagOptionParserTest/OPTION_INVALID_UNDEFINED_OPTION: -------------------------------------------------------------------------------- 1 | INVALID 2 | -------------------------------------------------------------------------------- /feature-flag/src/test/resources/tests/FeatureFlagOptionParserTest/OPTION_VALID_DUPLICATED: -------------------------------------------------------------------------------- 1 | PRIVATE PRIVATE 2 | PRIVATE OVERRIDABLE PRIVATE 3 | PRIVATE OVERRIDABLE PRIVATE OVERRIDABLE 4 | DEPRECATED DEPRECATED 5 | DEPRECATED PRIVATE DEPRECATED 6 | PRIVATE OVERRIDABLE DEPRECATED DEPRECATED 7 | PRIVATE OVERRIDABLE DEPRECATED PRIVATE OVERRIDABLE DEPRECATED 8 | -------------------------------------------------------------------------------- /feature-flag/src/test/resources/tests/FeatureFlagOptionParserTest/OPTION_VALID_NORMAL: -------------------------------------------------------------------------------- 1 | PRIVATE 2 | PRIVATE OVERRIDABLE 3 | DEPRECATED 4 | PRIVATE DEPRECATED 5 | PRIVATE OVERRIDABLE DEPRECATED 6 | -------------------------------------------------------------------------------- /feature-flag/src/test/resources/tests/FeatureFlagOptionParserTest/OPTION_VALID_SPACE: -------------------------------------------------------------------------------- 1 | PRIVATE 2 | PRIVATE 3 | PRIVATE OVERRIDABLE 4 | PRIVATE OVERRIDABLE 5 | PRIVATE OVERRIDABLE 6 | -------------------------------------------------------------------------------- /feature-flag/src/test/resources/tests/FeatureFlagSelectorParserTest/VALUE_INVALID_CONJUNCTION_EMPTY: -------------------------------------------------------------------------------- 1 | & 2 | -------------------------------------------------------------------------------- /feature-flag/src/test/resources/tests/FeatureFlagSelectorParserTest/VALUE_INVALID_CONJUNCTION_ONE_SIDE: -------------------------------------------------------------------------------- 1 | PHASE& 2 | -------------------------------------------------------------------------------- /feature-flag/src/test/resources/tests/FeatureFlagSelectorParserTest/VALUE_INVALID_DISJUNCTION_EMPTY: -------------------------------------------------------------------------------- 1 | , 2 | -------------------------------------------------------------------------------- /feature-flag/src/test/resources/tests/FeatureFlagSelectorParserTest/VALUE_INVALID_DISJUNCTION_ONE_SIDE: -------------------------------------------------------------------------------- 1 | PHASE, 2 | -------------------------------------------------------------------------------- /feature-flag/src/test/resources/tests/FeatureFlagSelectorParserTest/VALUE_INVALID_ELEMENT_LINK_ANOTHER: -------------------------------------------------------------------------------- 1 | another: 2 | -------------------------------------------------------------------------------- /feature-flag/src/test/resources/tests/FeatureFlagSelectorParserTest/VALUE_INVALID_ELEMENT_LINK_SELF: -------------------------------------------------------------------------------- 1 | : 2 | -------------------------------------------------------------------------------- /feature-flag/src/test/resources/tests/FeatureFlagSelectorParserTest/VALUE_INVALID_ELEMENT_USER: -------------------------------------------------------------------------------- 1 | @ 2 | -------------------------------------------------------------------------------- /feature-flag/src/test/resources/tests/FeatureFlagSelectorParserTest/VALUE_INVALID_ELEMENT_VERSION: -------------------------------------------------------------------------------- 1 | ~ 2 | -------------------------------------------------------------------------------- /feature-flag/src/test/resources/tests/FeatureFlagSelectorParserTest/VALUE_VALID_CONJUNCTION_WITH_SPACE: -------------------------------------------------------------------------------- 1 | PHASE1& PHASE2 2 | PHASE1 &PHASE2 3 | PHASE1 & PHASE2 4 | -------------------------------------------------------------------------------- /feature-flag/src/test/resources/tests/FeatureFlagSelectorParserTest/VALUE_VALID_DISJUNCTION_AND_CONJUNCTION: -------------------------------------------------------------------------------- 1 | PHASE1,PHASE2&PHASE3 2 | PHASE1&PHASE2,PHASE3 3 | PHASE1&PHASE2,PHASE3&PHASE4 4 | -------------------------------------------------------------------------------- /feature-flag/src/test/resources/tests/FeatureFlagSelectorParserTest/VALUE_VALID_DISJUNCTION_WITH_SPACE: -------------------------------------------------------------------------------- 1 | PHASE1,PHASE2 2 | PHASE1, PHASE2 3 | PHASE1 ,PHASE2 4 | PHASE1 , PHASE2 5 | -------------------------------------------------------------------------------- /feature-flag/src/test/resources/tests/FeatureFlagSelectorParserTest/VALUE_VALID_ELEMENT_TYPES: -------------------------------------------------------------------------------- 1 | PHASE 2 | @USER 3 | :SELF_LINK 4 | MODULE:LINK 5 | 1.2.3~ 6 | PHASE1&PHASE2 7 | -------------------------------------------------------------------------------- /feature-flag/src/test/resources/tests/FeatureFlagSelectorParserTest/VALUE_VALID_NESTED_MODULE_LINK: -------------------------------------------------------------------------------- 1 | NESTED:MODULE:LINK 2 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # Kotlin code style for this project: "official" or "obsolete": 15 | kotlin.code.style=official 16 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | 3 | feature-flag = "3.2.0" 4 | 5 | # https://developer.android.com/studio/releases/gradle-plugin 6 | android-gradle = "8.9.2" 7 | 8 | # Kotlin version should be fit with AGP's Kotlin version and Gradle's Kotlin version. 9 | # https://mvnrepository.com/artifact/com.android.tools.build/gradle 10 | # https://kotlinlang.org/docs/releases.html#release-details 11 | kotlin = "2.1.0" 12 | 13 | # https://github.com/JLLeitschuh/ktlint-gradle/releases 14 | ktlint-gradle = "12.2.0" 15 | 16 | # https://github.com/zafarkhaja/jsemver 17 | jsemver = "0.10.2" 18 | 19 | # https://github.com/ben-manes/gradle-versions-plugin/releases 20 | ben-manes-gradle = "0.52.0" 21 | 22 | # https://plugins.gradle.org/plugin/com.gradle.plugin-publish 23 | gradle-publish = "1.3.1" 24 | 25 | # https://github.com/spekframework/spek/releases 26 | spek = "2.0.19" 27 | 28 | # https://github.com/mockk/mockk/releases 29 | mockk = "1.14.0" 30 | 31 | 32 | [libraries] 33 | feature-flag = { module = "com.linecorp.android:com.linecorp.android.feature-flag", version.ref = "feature-flag" } 34 | 35 | android-gradle = { module = "com.android.tools.build:gradle", version.ref = "android-gradle" } 36 | kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } 37 | kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } 38 | kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } 39 | 40 | jsemver = { module = "com.github.zafarkhaja:java-semver", version.ref = "jsemver" } 41 | 42 | spek2-dsl-jvm = { module = "org.spekframework.spek2:spek-dsl-jvm", version.ref = "spek" } 43 | spek2-runner-junit5 = { module = "org.spekframework.spek2:spek-runner-junit5", version.ref = "spek" } 44 | 45 | mockk = { module = "io.mockk:mockk", version.ref = "mockk" } 46 | 47 | [plugins] 48 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 49 | ktlint-gradle = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint-gradle" } 50 | gradle-publish = { id = "com.gradle.plugin-publish", version.ref = "gradle-publish" } 51 | ben-manes-gradle = { id = "com.github.ben-manes.versions", version.ref = "ben-manes-gradle" } 52 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/feature-flag-android/3a08034a5bd520d160eec91bfe10f5b6761d050d/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /intellij-plugin/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | .intellijPlatform 4 | .qodana 5 | build 6 | 7 | !gradle/wrapper/gradle-wrapper.jar 8 | !src/main/gen/ 9 | !src/main/**/icon/ 10 | -------------------------------------------------------------------------------- /intellij-plugin/.run/Run Plugin.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | false 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /intellij-plugin/.run/Run Tests.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | true 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /intellij-plugin/.run/Run Verifications.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | false 23 | 24 | 25 | -------------------------------------------------------------------------------- /intellij-plugin/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # feature-flag-android-intellij-plugin Changelog 4 | 5 | ## [Unreleased] 6 | 7 | ## [1.0.0] 8 | 9 | ### Added 10 | 11 | - Initial release of Feature Flag Android IntelliJ plugin 12 | 13 | [Unreleased]: https://github.com/line/feature-flag-android/compare/v1.0.0...HEAD 14 | [1.0.0]: https://github.com/line/feature-flag-android/commits/v1.0.0 15 | -------------------------------------------------------------------------------- /intellij-plugin/README.md: -------------------------------------------------------------------------------- 1 | # feature-flag-android-intellij-plugin 2 | 3 | ## Overview 4 | 5 | 6 | This plugin provides high-level functionality in the IntelliJ IDE for the FeatureFlag definition 7 | files you create when using [feature-flag-android](https://github.com/line/feature-flag-android) a 8 | plugin for developing Android applications using FeatureFlag. 9 | 10 | Available features: 11 | 12 | - Syntax highlighting in the FeatureFlag file 13 | - Gutter icons in Java/Kotlin code to jump to FeatureFlag definition 14 | - Showing flag's documentation on Java/Kotlin code 15 | 16 | 17 | 18 | Please refer to the [README](../README.md) in the root folder for a description of the FeatureFlag 19 | itself, the license, and contribution 20 | guide, etc. 21 | 22 | ### Installation 23 | 24 | - Using the IDE built-in plugin system: 25 | 26 | Settings/Preferences > Plugins > Marketplace > Search for " 27 | FeatureFlag Android Support" > 28 | Install 29 | 30 | - Manually: 31 | 32 | Download the [latest release](https://github.com/line/feature-flag-android/releases/latest) and 33 | install it manually using 34 | Settings/Preferences > Plugins > ⚙️ > Install plugin from 35 | disk... 36 | -------------------------------------------------------------------------------- /intellij-plugin/gradle.properties: -------------------------------------------------------------------------------- 1 | # Opt-out flag for bundling Kotlin standard library -> https://jb.gg/intellij-platform-kotlin-stdlib 2 | kotlin.stdlib.default.dependency=false 3 | # Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html 4 | org.gradle.configuration-cache=true 5 | # Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html 6 | org.gradle.caching=true 7 | -------------------------------------------------------------------------------- /intellij-plugin/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | 3 | feature-flag-intellij-plugin = "1.0.0" 4 | 5 | # https://kotlinlang.org/docs/releases.html#release-details 6 | kotlin = "1.9.25" 7 | 8 | # https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin.html 9 | intellij-platform = "2.1.0" 10 | 11 | # https://github.com/JetBrains/gradle-changelog-plugin/releases 12 | changelog = "2.2.1" 13 | 14 | # https://github.com/Kotlin/kotlinx-kover/releases 15 | kover = "0.8.3" 16 | 17 | # https://www.jetbrains.com/help/qodana/qodana-gradle-plugin.html 18 | qodana = "2024.2.3" 19 | 20 | # https://github.com/JLLeitschuh/ktlint-gradle/releases 21 | ktlint-gradle = "10.0.0" 22 | 23 | # https://github.com/junit-team/junit4/releases 24 | junit = "4.13.2" 25 | 26 | [plugins] 27 | changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" } 28 | intellij-platform = { id = "org.jetbrains.intellij.platform", version.ref = "intellij-platform" } 29 | kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 30 | kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } 31 | qodana = { id = "org.jetbrains.qodana", version.ref = "qodana" } 32 | ktlint-gradle = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint-gradle" } 33 | 34 | [libraries] 35 | junit = { group = "junit", name = "junit", version.ref = "junit" } 36 | -------------------------------------------------------------------------------- /intellij-plugin/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/feature-flag-android/3a08034a5bd520d160eec91bfe10f5b6761d050d/intellij-plugin/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /intellij-plugin/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /intellij-plugin/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /intellij-plugin/qodana.yml: -------------------------------------------------------------------------------- 1 | # Qodana configuration: 2 | # https://www.jetbrains.com/help/qodana/qodana-yaml.html 3 | 4 | version: 1.0 5 | linter: jetbrains/qodana-jvm-community:2024.2 6 | projectJDK: "17" 7 | profile: 8 | name: qodana.recommended 9 | exclude: 10 | - name: All 11 | paths: 12 | - .qodana 13 | -------------------------------------------------------------------------------- /intellij-plugin/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "feature-flag-intellij-plugin" 2 | 3 | plugins { 4 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" 5 | } 6 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/gen/com/linecorp/android/featureflag/ij/plugin/psi/FeatureFlagEntry.java: -------------------------------------------------------------------------------- 1 | // This is a generated file. Not intended for manual editing. 2 | package com.linecorp.android.featureflag.ij.plugin.psi; 3 | 4 | import java.util.List; 5 | import org.jetbrains.annotations.*; 6 | import com.intellij.psi.PsiElement; 7 | 8 | public interface FeatureFlagEntry extends PsiElement { 9 | 10 | @Nullable 11 | FeatureFlagValueReference getValueReference(); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/gen/com/linecorp/android/featureflag/ij/plugin/psi/FeatureFlagModifier.java: -------------------------------------------------------------------------------- 1 | // This is a generated file. Not intended for manual editing. 2 | package com.linecorp.android.featureflag.ij.plugin.psi; 3 | 4 | import java.util.List; 5 | import org.jetbrains.annotations.*; 6 | import com.intellij.psi.PsiElement; 7 | 8 | public interface FeatureFlagModifier extends PsiElement { 9 | 10 | @Nullable 11 | FeatureFlagModifierType getModifierType(); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/gen/com/linecorp/android/featureflag/ij/plugin/psi/FeatureFlagProperty.java: -------------------------------------------------------------------------------- 1 | // This is a generated file. Not intended for manual editing. 2 | package com.linecorp.android.featureflag.ij.plugin.psi; 3 | 4 | import java.util.List; 5 | import org.jetbrains.annotations.*; 6 | import com.intellij.psi.PsiElement; 7 | import com.intellij.navigation.ItemPresentation; 8 | 9 | public interface FeatureFlagProperty extends FeatureFlagNamedElement { 10 | 11 | @Nullable 12 | FeatureFlagModifier getModifier(); 13 | 14 | @NotNull 15 | FeatureFlagValue getValue(); 16 | 17 | @Nullable 18 | String getKey(); 19 | 20 | @Nullable 21 | String getName(); 22 | 23 | @NotNull 24 | PsiElement setName(@NotNull String newName); 25 | 26 | @Nullable 27 | PsiElement getNameIdentifier(); 28 | 29 | @NotNull 30 | ItemPresentation getPresentation(); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/gen/com/linecorp/android/featureflag/ij/plugin/psi/FeatureFlagTypes.java: -------------------------------------------------------------------------------- 1 | // This is a generated file. Not intended for manual editing. 2 | package com.linecorp.android.featureflag.ij.plugin.psi; 3 | 4 | import com.intellij.psi.tree.IElementType; 5 | import com.intellij.psi.PsiElement; 6 | import com.intellij.lang.ASTNode; 7 | import com.linecorp.android.featureflag.ij.plugin.psi.impl.*; 8 | 9 | public interface FeatureFlagTypes { 10 | 11 | IElementType ENTRY = new FeatureFlagElementType("ENTRY"); 12 | IElementType MODIFIER = new FeatureFlagElementType("MODIFIER"); 13 | IElementType PROPERTY = new FeatureFlagElementType("PROPERTY"); 14 | IElementType VALUE = new FeatureFlagElementType("VALUE"); 15 | IElementType VALUE_REFERENCE = new FeatureFlagElementType("VALUE_REFERENCE"); 16 | 17 | IElementType AND = new FeatureFlagTokenType("AND"); 18 | IElementType COLON = new FeatureFlagTokenType("COLON"); 19 | IElementType COMMA = new FeatureFlagTokenType("COMMA"); 20 | IElementType COMMENT = new FeatureFlagTokenType("COMMENT"); 21 | IElementType CRLF = new FeatureFlagTokenType("CRLF"); 22 | IElementType KEY = new FeatureFlagTokenType("KEY"); 23 | IElementType MODIFIER_LITERALIZE = new FeatureFlagTokenType("MODIFIER_LITERALIZE"); 24 | IElementType MODIFIER_OVERRIDABLE = new FeatureFlagTokenType("MODIFIER_OVERRIDABLE"); 25 | IElementType MODIFIER_PRIVATE = new FeatureFlagTokenType("MODIFIER_PRIVATE"); 26 | IElementType SEPARATOR = new FeatureFlagTokenType("SEPARATOR"); 27 | IElementType VALUE_REFERENCE_NAME = new FeatureFlagTokenType("VALUE_REFERENCE_NAME"); 28 | IElementType VALUE_REFERENCE_PACKAGE = new FeatureFlagTokenType("VALUE_REFERENCE_PACKAGE"); 29 | IElementType VALUE_STRING = new FeatureFlagTokenType("VALUE_STRING"); 30 | IElementType VALUE_USERNAME = new FeatureFlagTokenType("VALUE_USERNAME"); 31 | IElementType VALUE_VERSION = new FeatureFlagTokenType("VALUE_VERSION"); 32 | 33 | class Factory { 34 | public static PsiElement createElement(ASTNode node) { 35 | IElementType type = node.getElementType(); 36 | if (type == ENTRY) { 37 | return new FeatureFlagEntryImpl(node); 38 | } 39 | else if (type == MODIFIER) { 40 | return new FeatureFlagModifierImpl(node); 41 | } 42 | else if (type == PROPERTY) { 43 | return new FeatureFlagPropertyImpl(node); 44 | } 45 | else if (type == VALUE) { 46 | return new FeatureFlagValueImpl(node); 47 | } 48 | else if (type == VALUE_REFERENCE) { 49 | return new FeatureFlagValueReferenceImpl(node); 50 | } 51 | throw new AssertionError("Unknown element type: " + type); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/gen/com/linecorp/android/featureflag/ij/plugin/psi/FeatureFlagValue.java: -------------------------------------------------------------------------------- 1 | // This is a generated file. Not intended for manual editing. 2 | package com.linecorp.android.featureflag.ij.plugin.psi; 3 | 4 | import java.util.List; 5 | import org.jetbrains.annotations.*; 6 | import com.intellij.psi.PsiElement; 7 | 8 | public interface FeatureFlagValue extends PsiElement { 9 | 10 | @NotNull 11 | List getEntryList(); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/gen/com/linecorp/android/featureflag/ij/plugin/psi/FeatureFlagValueReference.java: -------------------------------------------------------------------------------- 1 | // This is a generated file. Not intended for manual editing. 2 | package com.linecorp.android.featureflag.ij.plugin.psi; 3 | 4 | import java.util.List; 5 | import org.jetbrains.annotations.*; 6 | import com.intellij.psi.PsiElement; 7 | 8 | public interface FeatureFlagValueReference extends PsiElement { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/gen/com/linecorp/android/featureflag/ij/plugin/psi/FeatureFlagVisitor.java: -------------------------------------------------------------------------------- 1 | // This is a generated file. Not intended for manual editing. 2 | package com.linecorp.android.featureflag.ij.plugin.psi; 3 | 4 | import org.jetbrains.annotations.*; 5 | import com.intellij.psi.PsiElementVisitor; 6 | import com.intellij.psi.PsiElement; 7 | 8 | public class FeatureFlagVisitor extends PsiElementVisitor { 9 | 10 | public void visitEntry(@NotNull FeatureFlagEntry o) { 11 | visitPsiElement(o); 12 | } 13 | 14 | public void visitModifier(@NotNull FeatureFlagModifier o) { 15 | visitPsiElement(o); 16 | } 17 | 18 | public void visitProperty(@NotNull FeatureFlagProperty o) { 19 | visitNamedElement(o); 20 | } 21 | 22 | public void visitValue(@NotNull FeatureFlagValue o) { 23 | visitPsiElement(o); 24 | } 25 | 26 | public void visitValueReference(@NotNull FeatureFlagValueReference o) { 27 | visitPsiElement(o); 28 | } 29 | 30 | public void visitNamedElement(@NotNull FeatureFlagNamedElement o) { 31 | visitPsiElement(o); 32 | } 33 | 34 | public void visitPsiElement(@NotNull PsiElement o) { 35 | visitElement(o); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/gen/com/linecorp/android/featureflag/ij/plugin/psi/impl/FeatureFlagEntryImpl.java: -------------------------------------------------------------------------------- 1 | // This is a generated file. Not intended for manual editing. 2 | package com.linecorp.android.featureflag.ij.plugin.psi.impl; 3 | 4 | import java.util.List; 5 | import org.jetbrains.annotations.*; 6 | import com.intellij.lang.ASTNode; 7 | import com.intellij.psi.PsiElement; 8 | import com.intellij.psi.PsiElementVisitor; 9 | import com.intellij.psi.util.PsiTreeUtil; 10 | import static com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagTypes.*; 11 | import com.intellij.extapi.psi.ASTWrapperPsiElement; 12 | import com.linecorp.android.featureflag.ij.plugin.psi.*; 13 | 14 | public class FeatureFlagEntryImpl extends ASTWrapperPsiElement implements FeatureFlagEntry { 15 | 16 | public FeatureFlagEntryImpl(@NotNull ASTNode node) { 17 | super(node); 18 | } 19 | 20 | public void accept(@NotNull FeatureFlagVisitor visitor) { 21 | visitor.visitEntry(this); 22 | } 23 | 24 | @Override 25 | public void accept(@NotNull PsiElementVisitor visitor) { 26 | if (visitor instanceof FeatureFlagVisitor) accept((FeatureFlagVisitor)visitor); 27 | else super.accept(visitor); 28 | } 29 | 30 | @Override 31 | @Nullable 32 | public FeatureFlagValueReference getValueReference() { 33 | return findChildByClass(FeatureFlagValueReference.class); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/gen/com/linecorp/android/featureflag/ij/plugin/psi/impl/FeatureFlagModifierImpl.java: -------------------------------------------------------------------------------- 1 | // This is a generated file. Not intended for manual editing. 2 | package com.linecorp.android.featureflag.ij.plugin.psi.impl; 3 | 4 | import java.util.List; 5 | import org.jetbrains.annotations.*; 6 | import com.intellij.lang.ASTNode; 7 | import com.intellij.psi.PsiElement; 8 | import com.intellij.psi.PsiElementVisitor; 9 | import com.intellij.psi.util.PsiTreeUtil; 10 | import static com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagTypes.*; 11 | import com.intellij.extapi.psi.ASTWrapperPsiElement; 12 | import com.linecorp.android.featureflag.ij.plugin.psi.*; 13 | 14 | public class FeatureFlagModifierImpl extends ASTWrapperPsiElement implements FeatureFlagModifier { 15 | 16 | public FeatureFlagModifierImpl(@NotNull ASTNode node) { 17 | super(node); 18 | } 19 | 20 | public void accept(@NotNull FeatureFlagVisitor visitor) { 21 | visitor.visitModifier(this); 22 | } 23 | 24 | @Override 25 | public void accept(@NotNull PsiElementVisitor visitor) { 26 | if (visitor instanceof FeatureFlagVisitor) accept((FeatureFlagVisitor)visitor); 27 | else super.accept(visitor); 28 | } 29 | 30 | @Override 31 | @Nullable 32 | public FeatureFlagModifierType getModifierType() { 33 | return FeatureFlagPsiImplUtil.getModifierType(this); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/gen/com/linecorp/android/featureflag/ij/plugin/psi/impl/FeatureFlagPropertyImpl.java: -------------------------------------------------------------------------------- 1 | // This is a generated file. Not intended for manual editing. 2 | package com.linecorp.android.featureflag.ij.plugin.psi.impl; 3 | 4 | import java.util.List; 5 | import org.jetbrains.annotations.*; 6 | import com.intellij.lang.ASTNode; 7 | import com.intellij.psi.PsiElement; 8 | import com.intellij.psi.PsiElementVisitor; 9 | import com.intellij.psi.util.PsiTreeUtil; 10 | import static com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagTypes.*; 11 | import com.linecorp.android.featureflag.ij.plugin.psi.*; 12 | import com.intellij.navigation.ItemPresentation; 13 | 14 | public class FeatureFlagPropertyImpl extends FeatureFlagNamedElementImpl implements FeatureFlagProperty { 15 | 16 | public FeatureFlagPropertyImpl(@NotNull ASTNode node) { 17 | super(node); 18 | } 19 | 20 | public void accept(@NotNull FeatureFlagVisitor visitor) { 21 | visitor.visitProperty(this); 22 | } 23 | 24 | @Override 25 | public void accept(@NotNull PsiElementVisitor visitor) { 26 | if (visitor instanceof FeatureFlagVisitor) accept((FeatureFlagVisitor)visitor); 27 | else super.accept(visitor); 28 | } 29 | 30 | @Override 31 | @Nullable 32 | public FeatureFlagModifier getModifier() { 33 | return findChildByClass(FeatureFlagModifier.class); 34 | } 35 | 36 | @Override 37 | @NotNull 38 | public FeatureFlagValue getValue() { 39 | return findNotNullChildByClass(FeatureFlagValue.class); 40 | } 41 | 42 | @Override 43 | @Nullable 44 | public String getKey() { 45 | return FeatureFlagPsiImplUtil.getKey(this); 46 | } 47 | 48 | @Override 49 | @Nullable 50 | public String getName() { 51 | return FeatureFlagPsiImplUtil.getName(this); 52 | } 53 | 54 | @Override 55 | @NotNull 56 | public PsiElement setName(@NotNull String newName) { 57 | return FeatureFlagPsiImplUtil.setName(this, newName); 58 | } 59 | 60 | @Override 61 | @Nullable 62 | public PsiElement getNameIdentifier() { 63 | return FeatureFlagPsiImplUtil.getNameIdentifier(this); 64 | } 65 | 66 | @Override 67 | @NotNull 68 | public ItemPresentation getPresentation() { 69 | return FeatureFlagPsiImplUtil.getPresentation(this); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/gen/com/linecorp/android/featureflag/ij/plugin/psi/impl/FeatureFlagValueImpl.java: -------------------------------------------------------------------------------- 1 | // This is a generated file. Not intended for manual editing. 2 | package com.linecorp.android.featureflag.ij.plugin.psi.impl; 3 | 4 | import java.util.List; 5 | import org.jetbrains.annotations.*; 6 | import com.intellij.lang.ASTNode; 7 | import com.intellij.psi.PsiElement; 8 | import com.intellij.psi.PsiElementVisitor; 9 | import com.intellij.psi.util.PsiTreeUtil; 10 | import static com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagTypes.*; 11 | import com.intellij.extapi.psi.ASTWrapperPsiElement; 12 | import com.linecorp.android.featureflag.ij.plugin.psi.*; 13 | 14 | public class FeatureFlagValueImpl extends ASTWrapperPsiElement implements FeatureFlagValue { 15 | 16 | public FeatureFlagValueImpl(@NotNull ASTNode node) { 17 | super(node); 18 | } 19 | 20 | public void accept(@NotNull FeatureFlagVisitor visitor) { 21 | visitor.visitValue(this); 22 | } 23 | 24 | @Override 25 | public void accept(@NotNull PsiElementVisitor visitor) { 26 | if (visitor instanceof FeatureFlagVisitor) accept((FeatureFlagVisitor)visitor); 27 | else super.accept(visitor); 28 | } 29 | 30 | @Override 31 | @NotNull 32 | public List getEntryList() { 33 | return PsiTreeUtil.getChildrenOfTypeAsList(this, FeatureFlagEntry.class); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/gen/com/linecorp/android/featureflag/ij/plugin/psi/impl/FeatureFlagValueReferenceImpl.java: -------------------------------------------------------------------------------- 1 | // This is a generated file. Not intended for manual editing. 2 | package com.linecorp.android.featureflag.ij.plugin.psi.impl; 3 | 4 | import java.util.List; 5 | import org.jetbrains.annotations.*; 6 | import com.intellij.lang.ASTNode; 7 | import com.intellij.psi.PsiElement; 8 | import com.intellij.psi.PsiElementVisitor; 9 | import com.intellij.psi.util.PsiTreeUtil; 10 | import static com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagTypes.*; 11 | import com.intellij.extapi.psi.ASTWrapperPsiElement; 12 | import com.linecorp.android.featureflag.ij.plugin.psi.*; 13 | 14 | public class FeatureFlagValueReferenceImpl extends ASTWrapperPsiElement implements FeatureFlagValueReference { 15 | 16 | public FeatureFlagValueReferenceImpl(@NotNull ASTNode node) { 17 | super(node); 18 | } 19 | 20 | public void accept(@NotNull FeatureFlagVisitor visitor) { 21 | visitor.visitValueReference(this); 22 | } 23 | 24 | @Override 25 | public void accept(@NotNull PsiElementVisitor visitor) { 26 | if (visitor instanceof FeatureFlagVisitor) accept((FeatureFlagVisitor)visitor); 27 | else super.accept(visitor); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/FeatureFlag.bnf: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | { 18 | parserClass="com.linecorp.android.featureflag.ij.plugin.parser.FeatureFlagParser" 19 | 20 | extends="com.intellij.extapi.psi.ASTWrapperPsiElement" 21 | 22 | psiClassPrefix="FeatureFlag" 23 | psiImplClassSuffix="Impl" 24 | psiPackage="com.linecorp.android.featureflag.ij.plugin.psi" 25 | psiImplPackage="com.linecorp.android.featureflag.ij.plugin.psi.impl" 26 | 27 | elementTypeHolderClass="com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagTypes" 28 | elementTypeClass="com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagElementType" 29 | tokenTypeClass="com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagTokenType" 30 | 31 | psiImplUtilClass="com.linecorp.android.featureflag.ij.plugin.psi.impl.FeatureFlagPsiImplUtil" 32 | } 33 | 34 | featureFlagFile ::= item_* 35 | 36 | private item_ ::= (property|COMMENT|CRLF) 37 | 38 | property ::= [modifier] KEY SEPARATOR value COMMENT? 39 | { 40 | mixin="com.linecorp.android.featureflag.ij.plugin.psi.impl.FeatureFlagNamedElementImpl" 41 | implements="com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagNamedElement" 42 | methods=[getKey getName setName getNameIdentifier getPresentation] 43 | } 44 | 45 | modifier ::= MODIFIER_OVERRIDABLE | MODIFIER_PRIVATE | MODIFIER_LITERALIZE 46 | { 47 | methods=[getModifierType] 48 | } 49 | 50 | value ::= (entry (COMMA | AND))* entry 51 | 52 | entry ::= (VALUE_VERSION | VALUE_USERNAME | valueReference | VALUE_STRING) 53 | 54 | valueReference ::= VALUE_REFERENCE_PACKAGE COLON VALUE_REFERENCE_NAME 55 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/FeatureFlag.flex: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin.lexer; 18 | 19 | import com.intellij.lexer.FlexLexer; 20 | import com.intellij.psi.tree.IElementType; 21 | import com.intellij.psi.TokenType; 22 | import com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagTypes; 23 | import kotlin.text.StringsKt; 24 | 25 | %% 26 | 27 | %class FeatureFlagFlexLexer 28 | %implements FlexLexer 29 | %unicode 30 | %function advance 31 | %type IElementType 32 | %eof{ return; 33 | %eof} 34 | 35 | LINE_TERMINATOR = \r|\n|\r\n 36 | INPUT_CHARACTER = [^\r\n] 37 | WHITE_SPACE = {LINE_TERMINATOR} | [ \t\f] 38 | 39 | CRLF=\R 40 | VALUE_CHARACTER=[^ \t\f\n\f\\#,&] 41 | PHASE_CHARACTERS = [a-zA-Z_-][a-zA-Z0-9_-]* 42 | VERSION_CHARACTERS = [a-zA-Z0-9_\.\~\-] 43 | LINE_COMMENT = #[^\r\n]*[\r\n]? 44 | SEPARATOR = "=" 45 | KEY_CHARACTERS = [a-zA-Z_][a-zA-Z0-9_]* 46 | MODIFIER_OVERRIDABLE = "OVERRIDABLE" 47 | MODIFIER_PRIVATE = "PRIVATE" 48 | MODIFIER_LITERALIZE = "LITERALIZE" 49 | COMMA = "," 50 | AND = "&" 51 | COLON = ":" 52 | 53 | %state WAITING_SEPARATOR 54 | %state WAITING_VALUE 55 | %state WAITING_NEXT_VALUE_OR_CRLF_OR_COMMENT 56 | %state WAITING_VALUE_REFERENCE_SEPARATOR 57 | %state WAITING_VALUE_REFERENCE_NAME 58 | 59 | %% 60 | 61 | {LINE_COMMENT} { yybegin(YYINITIAL); return FeatureFlagTypes.COMMENT; } 62 | 63 | {MODIFIER_OVERRIDABLE} { yybegin(YYINITIAL); return FeatureFlagTypes.MODIFIER_OVERRIDABLE; } 64 | 65 | {MODIFIER_PRIVATE} { yybegin(YYINITIAL); return FeatureFlagTypes.MODIFIER_PRIVATE; } 66 | 67 | {MODIFIER_LITERALIZE} { yybegin(YYINITIAL); return FeatureFlagTypes.MODIFIER_LITERALIZE; } 68 | 69 | {KEY_CHARACTERS} { yybegin(WAITING_SEPARATOR); return FeatureFlagTypes.KEY; } 70 | 71 | {SEPARATOR} { yybegin(WAITING_VALUE); return FeatureFlagTypes.SEPARATOR; } 72 | 73 | {VALUE_CHARACTER}+ { 74 | CharSequence text = yytext().toString(); 75 | if (StringsKt.startsWith(text, '@', false)) { 76 | yybegin(WAITING_NEXT_VALUE_OR_CRLF_OR_COMMENT); 77 | return FeatureFlagTypes.VALUE_USERNAME; 78 | } else if (StringsKt.endsWith(text, '~', false)) { 79 | yybegin(WAITING_NEXT_VALUE_OR_CRLF_OR_COMMENT); 80 | return FeatureFlagTypes.VALUE_VERSION; 81 | } 82 | int colonIndex = text.toString().indexOf(':'); 83 | if (colonIndex == -1) { 84 | yybegin(WAITING_NEXT_VALUE_OR_CRLF_OR_COMMENT); 85 | return FeatureFlagTypes.VALUE_STRING; 86 | } 87 | yypushback(text.length() - colonIndex); 88 | yybegin(WAITING_VALUE_REFERENCE_SEPARATOR); 89 | return FeatureFlagTypes.VALUE_REFERENCE_PACKAGE; 90 | } 91 | 92 | {COLON} { yybegin(WAITING_VALUE_REFERENCE_NAME); return FeatureFlagTypes.COLON; } 93 | 94 | {VALUE_CHARACTER}+ { yybegin(WAITING_NEXT_VALUE_OR_CRLF_OR_COMMENT); return FeatureFlagTypes.VALUE_REFERENCE_NAME; } 95 | 96 | {COMMA} { yybegin(WAITING_VALUE); return FeatureFlagTypes.COMMA; } 97 | 98 | {AND} { yybegin(WAITING_VALUE); return FeatureFlagTypes.AND; } 99 | 100 | {LINE_TERMINATOR}+ { yybegin(YYINITIAL); return TokenType.WHITE_SPACE; } 101 | 102 | {LINE_COMMENT} { yybegin(YYINITIAL); return FeatureFlagTypes.COMMENT; } 103 | 104 | {WHITE_SPACE}+ { return TokenType.WHITE_SPACE; } 105 | 106 | {LINE_TERMINATOR} { yybegin(YYINITIAL); return TokenType.WHITE_SPACE; } 107 | 108 | [^] { return TokenType.BAD_CHARACTER; } 109 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/FeatureFlagBundle.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin 18 | 19 | import com.intellij.DynamicBundle 20 | import org.jetbrains.annotations.Nls 21 | import org.jetbrains.annotations.NonNls 22 | import org.jetbrains.annotations.PropertyKey 23 | 24 | /** 25 | * A message bundle for feature flag plugin. 26 | */ 27 | @NonNls 28 | object FeatureFlagBundle { 29 | private const val BUNDLE = "messages.FeatureFlagBundle" 30 | private val INSTANCE = DynamicBundle(FeatureFlagBundle::class.java, BUNDLE) 31 | 32 | @Nls 33 | fun message( 34 | @PropertyKey(resourceBundle = BUNDLE) key: String, 35 | vararg params: Any 36 | ): String = INSTANCE.getMessage(key, *params) 37 | } 38 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/FeatureFlagFileType.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin 18 | 19 | import com.intellij.openapi.fileTypes.LanguageFileType 20 | import com.linecorp.android.featureflag.ij.plugin.icon.FeatureFlagIcons 21 | import javax.swing.Icon 22 | 23 | /** 24 | * An implementation of [LanguageFileType] for feature flag language. 25 | */ 26 | object FeatureFlagFileType : LanguageFileType(FeatureFlagLanguage) { 27 | 28 | override fun getName(): String = "FeatureFlag File" 29 | 30 | override fun getDescription(): String = "FeatureFlag language file" 31 | 32 | override fun getDefaultExtension(): String = "ff" 33 | 34 | override fun getIcon(): Icon = FeatureFlagIcons.FILE 35 | } 36 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/FeatureFlagLanguage.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin 18 | 19 | import com.intellij.lang.Language 20 | 21 | /** 22 | * A language definition for feature flag. 23 | */ 24 | object FeatureFlagLanguage : Language("FeatureFlag") { 25 | private fun readResolve(): Any = FeatureFlagLanguage 26 | } 27 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/FeatureFlagUtil.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin 18 | 19 | import com.intellij.openapi.project.Project 20 | import com.intellij.openapi.vfs.VirtualFile 21 | import com.intellij.psi.PsiComment 22 | import com.intellij.psi.PsiManager 23 | import com.intellij.psi.search.FileTypeIndex 24 | import com.intellij.psi.search.GlobalSearchScope 25 | import com.intellij.psi.util.PsiTreeUtil 26 | import com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagFile 27 | import com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagProperty 28 | 29 | /** 30 | * A collection of utility functions for feature flag plugin. 31 | */ 32 | object FeatureFlagUtil { 33 | fun findProperties(project: Project, key: String): List { 34 | val psiManager = PsiManager.getInstance(project) 35 | val virtualFiles = FileTypeIndex.getFiles( 36 | FeatureFlagFileType, 37 | GlobalSearchScope.allScope(project) 38 | ) 39 | return virtualFiles.getAllFeatureFlagProperties(psiManager) 40 | .flatMap { properties -> properties.filter { it.key == key } } 41 | .toList() 42 | } 43 | 44 | fun findProperties(project: Project): List { 45 | val psiManager = PsiManager.getInstance(project) 46 | val virtualFiles = FileTypeIndex.getFiles( 47 | FeatureFlagFileType, 48 | GlobalSearchScope.allScope(project) 49 | ) 50 | return virtualFiles.getAllFeatureFlagProperties(psiManager) 51 | .flatMap { it.asList() } 52 | .toList() 53 | } 54 | 55 | fun findDocumentationComment(property: FeatureFlagProperty): String { 56 | val commentLines = 57 | generateSequence(property.prevSibling as? PsiComment) { it.prevSibling as? PsiComment } 58 | .map { it.text.replaceFirst("#", "") } 59 | .toList() 60 | .reversed() 61 | val commentBuilder = StringBuilder() 62 | var currentParagraphIndentDepth: Int? = null 63 | for (line in commentLines) { 64 | val indentDepth = line.takeWhile { it == ' ' }.length 65 | // If a line break is included, it is basically recognized as a new line, but if a 66 | // single space is added at the beginning, it is recognized as a continuation of the 67 | // previous line. 68 | when (currentParagraphIndentDepth) { 69 | null -> currentParagraphIndentDepth = indentDepth 70 | indentDepth - 1 -> commentBuilder.append(" ") 71 | else -> commentBuilder.append("\n") 72 | } 73 | commentBuilder.append(line.drop(indentDepth).trimEnd()) 74 | } 75 | return commentBuilder.toString() 76 | } 77 | 78 | private fun Collection.getAllFeatureFlagProperties( 79 | psiManager: PsiManager 80 | ): Sequence> = asSequence() 81 | .filterNotNull() 82 | .mapNotNull { psiManager.findFile(it) as? FeatureFlagFile } 83 | .mapNotNull { PsiTreeUtil.getChildrenOfType(it, FeatureFlagProperty::class.java) } 84 | } 85 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/commenter/FeatureFlagCommenter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin.commenter 18 | 19 | import com.intellij.lang.Commenter 20 | 21 | /** 22 | * A class to provide shortcut comment-out functionality on the editor. 23 | */ 24 | class FeatureFlagCommenter : Commenter { 25 | override fun getLineCommentPrefix(): String = "#" 26 | 27 | override fun getBlockCommentPrefix(): String? = null 28 | 29 | override fun getBlockCommentSuffix(): String? = null 30 | 31 | override fun getCommentedBlockCommentPrefix(): String? = null 32 | 33 | override fun getCommentedBlockCommentSuffix(): String? = null 34 | } 35 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/gotosymbol/FeatureFlagChooseByNameContributor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin.gotosymbol 18 | 19 | import com.intellij.navigation.ChooseByNameContributorEx 20 | import com.intellij.navigation.NavigationItem 21 | import com.intellij.psi.search.GlobalSearchScope 22 | import com.intellij.util.Processor 23 | import com.intellij.util.indexing.FindSymbolParameters 24 | import com.intellij.util.indexing.IdFilter 25 | import com.linecorp.android.featureflag.ij.plugin.FeatureFlagUtil 26 | 27 | /** 28 | * A class for providing feature flag properties for "Navigate | Symbol" search results. 29 | */ 30 | class FeatureFlagChooseByNameContributor : ChooseByNameContributorEx { 31 | 32 | override fun processNames( 33 | processor: Processor, 34 | scope: GlobalSearchScope, 35 | filter: IdFilter? 36 | ) { 37 | val project = checkNotNull(scope.project) 38 | val properties = FeatureFlagUtil.findProperties(project) 39 | for (property in properties) { 40 | processor.process(property.getKey()) 41 | } 42 | } 43 | 44 | override fun processElementsWithName( 45 | name: String, 46 | processor: Processor, 47 | parameters: FindSymbolParameters 48 | ) { 49 | val properties = FeatureFlagUtil.findProperties(parameters.project, name) 50 | for (property in properties) { 51 | processor.process(property) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/icon/FeatureFlagIcons.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin.icon 18 | 19 | import com.intellij.openapi.util.IconLoader 20 | import javax.swing.Icon 21 | 22 | /** 23 | * A list of icons to be used in the plugin. 24 | */ 25 | object FeatureFlagIcons { 26 | val FILE: Icon = 27 | IconLoader.getIcon("icons/featureFlagFile.svg", FeatureFlagIcons::class.java) 28 | val SINGLE_GUTTER: Icon = 29 | IconLoader.getIcon("icons/featureFlagSingleGutter.svg", FeatureFlagIcons::class.java) 30 | val SINGLE_PROPERTY: Icon = 31 | IconLoader.getIcon("icons/featureFlagSingleProperty.svg", FeatureFlagIcons::class.java) 32 | val STRUCTURE_VIEW_OVERRIDABLE: Icon = 33 | IconLoader.getIcon( 34 | "icons/featureFlagStructureViewOverridable.svg", 35 | FeatureFlagIcons::class.java 36 | ) 37 | val STRUCTURE_VIEW_LITERALIZE: Icon = 38 | IconLoader.getIcon( 39 | "icons/featureFlagStructureViewLiteralize.svg", 40 | FeatureFlagIcons::class.java 41 | ) 42 | val STRUCTURE_VIEW_PRIVATE: Icon = 43 | IconLoader.getIcon( 44 | "icons/featureFlagStructureViewPrivate.svg", 45 | FeatureFlagIcons::class.java 46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/icon/FeatureFlagPropertyIconProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin.icon 18 | 19 | import com.intellij.ide.IconProvider 20 | import com.intellij.psi.PsiElement 21 | import com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagProperty 22 | import javax.swing.Icon 23 | 24 | /** 25 | * An implementation of [IconProvider] which provides icons for [FeatureFlagProperty] elements. 26 | */ 27 | class FeatureFlagPropertyIconProvider : IconProvider() { 28 | override fun getIcon(element: PsiElement, flags: Int): Icon? = when (element) { 29 | is FeatureFlagProperty -> FeatureFlagIcons.SINGLE_PROPERTY 30 | else -> null 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/lexer/FeatureFlagLexer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin.lexer 18 | 19 | import com.intellij.lexer.FlexAdapter 20 | 21 | /** 22 | * A lexer for the feature flag language. 23 | */ 24 | class FeatureFlagLexer : FlexAdapter(FeatureFlagFlexLexer(null)) 25 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/linemarker/FeatureFlagJavaLineMarkerProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin.linemarker 18 | 19 | import com.intellij.psi.PsiElement 20 | import com.intellij.psi.PsiField 21 | import com.intellij.psi.PsiIdentifier 22 | import com.intellij.psi.PsiReferenceExpression 23 | 24 | /** 25 | * An implementation of [FeatureFlagLineMarkerProvider] for Java. 26 | */ 27 | class FeatureFlagJavaLineMarkerProvider : FeatureFlagLineMarkerProvider() { 28 | 29 | override fun isFeatureFlagPropertyIdentifier(element: PsiElement): Boolean { 30 | if (element !is PsiIdentifier) { 31 | return false 32 | } 33 | val parent = element.parent as? PsiReferenceExpression ?: return false 34 | val resolvedField = parent.resolve() as? PsiField ?: return false 35 | return isFeatureFlagField(resolvedField) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/linemarker/FeatureFlagKotlinLineMarkerProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin.linemarker 18 | 19 | import com.intellij.psi.PsiElement 20 | import org.jetbrains.kotlin.idea.references.mainReference 21 | import org.jetbrains.kotlin.psi.KtNameReferenceExpression 22 | import com.intellij.psi.PsiField 23 | import com.intellij.psi.impl.source.tree.LeafPsiElement 24 | import org.jetbrains.kotlin.lexer.KtTokens 25 | 26 | /** 27 | * An implementation of [FeatureFlagLineMarkerProvider] for Kotlin. 28 | */ 29 | class FeatureFlagKotlinLineMarkerProvider : FeatureFlagLineMarkerProvider() { 30 | 31 | override fun isFeatureFlagPropertyIdentifier(element: PsiElement): Boolean { 32 | if (element !is LeafPsiElement || element.elementType != KtTokens.IDENTIFIER) { 33 | return false 34 | } 35 | val entireFlagNameReferenceExpression = 36 | element.parent as? KtNameReferenceExpression ?: return false 37 | val resolvedField = 38 | entireFlagNameReferenceExpression.mainReference.resolve() as? PsiField ?: return false 39 | return isFeatureFlagField(resolvedField) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/linemarker/FeatureFlagLineMarkerProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin.linemarker 18 | 19 | import com.intellij.codeInsight.daemon.RelatedItemLineMarkerInfo 20 | import com.intellij.codeInsight.daemon.RelatedItemLineMarkerProvider 21 | import com.intellij.codeInsight.navigation.NavigationGutterIconBuilder 22 | import com.intellij.psi.PsiElement 23 | import com.intellij.psi.PsiField 24 | import com.linecorp.android.featureflag.ij.plugin.FeatureFlagBundle 25 | import com.linecorp.android.featureflag.ij.plugin.icon.FeatureFlagIcons 26 | import com.linecorp.android.featureflag.ij.plugin.FeatureFlagUtil 27 | 28 | /** 29 | * Class for adding an icon to the gutter to jump to the referenced feature flag from the source 30 | * code. 31 | */ 32 | abstract class FeatureFlagLineMarkerProvider : RelatedItemLineMarkerProvider() { 33 | 34 | abstract fun isFeatureFlagPropertyIdentifier(element: PsiElement): Boolean 35 | 36 | override fun collectNavigationMarkers( 37 | element: PsiElement, 38 | result: MutableCollection> 39 | ) { 40 | if (!isFeatureFlagPropertyIdentifier(element)) { 41 | return 42 | } 43 | val properties = FeatureFlagUtil.findProperties(element.project, element.text) 44 | if (properties.isEmpty()) { 45 | return 46 | } 47 | val builder = NavigationGutterIconBuilder.create(FeatureFlagIcons.SINGLE_GUTTER) 48 | .setTargets(properties) 49 | .setTooltipText(FeatureFlagBundle.message("lineMarker.tooltip")) 50 | result.add(builder.createLineMarkerInfo(element)) 51 | } 52 | 53 | protected companion object { 54 | private const val FEATURE_FLAG_CLASS_SUFFIX = "FeatureFlag" 55 | 56 | fun isFeatureFlagField(psiField: PsiField): Boolean { 57 | val flagContainingClassName = psiField.containingClass?.name ?: return false 58 | return flagContainingClassName.endsWith(FEATURE_FLAG_CLASS_SUFFIX) 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/navigationbar/FeatureFlagStructureAwareNavbar.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin.navigationbar 18 | 19 | import com.intellij.ide.navigationToolbar.StructureAwareNavBarModelExtension 20 | import com.intellij.lang.Language 21 | import com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagFile 22 | import com.linecorp.android.featureflag.ij.plugin.FeatureFlagLanguage 23 | import com.linecorp.android.featureflag.ij.plugin.icon.FeatureFlagIcons 24 | import com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagProperty 25 | import javax.swing.Icon 26 | 27 | /** 28 | * A class for providing navigation bar data when opening feature flag file. 29 | */ 30 | class FeatureFlagStructureAwareNavbar : StructureAwareNavBarModelExtension() { 31 | 32 | override val language: Language = FeatureFlagLanguage 33 | 34 | override fun getPresentableText(any: Any?): String? { 35 | if (any is FeatureFlagFile) { 36 | return any.name 37 | } 38 | if (any is FeatureFlagProperty) { 39 | return any.key 40 | } 41 | return null 42 | } 43 | 44 | override fun getIcon(any: Any?): Icon? { 45 | if (any is FeatureFlagProperty) { 46 | return FeatureFlagIcons.SINGLE_PROPERTY 47 | } 48 | return null 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/parser/FeatureFlagParserDefinition.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin.parser 18 | 19 | import com.intellij.lang.ASTNode 20 | import com.intellij.lang.ParserDefinition 21 | import com.intellij.lang.PsiParser 22 | import com.intellij.lexer.Lexer 23 | import com.intellij.openapi.project.Project 24 | import com.intellij.psi.FileViewProvider 25 | import com.intellij.psi.PsiElement 26 | import com.intellij.psi.PsiFile 27 | import com.intellij.psi.tree.IFileElementType 28 | import com.intellij.psi.tree.TokenSet 29 | import com.linecorp.android.featureflag.ij.plugin.FeatureFlagLanguage 30 | import com.linecorp.android.featureflag.ij.plugin.lexer.FeatureFlagLexer 31 | import com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagFile 32 | import com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagTokenSets 33 | import com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagTypes 34 | 35 | /** 36 | * An implementation of [ParserDefinition] for feature flag language. 37 | */ 38 | class FeatureFlagParserDefinition : ParserDefinition { 39 | 40 | override fun createLexer(project: Project?): Lexer = FeatureFlagLexer() 41 | 42 | override fun getCommentTokens(): TokenSet = FeatureFlagTokenSets.COMMENTS 43 | 44 | override fun getStringLiteralElements(): TokenSet = TokenSet.EMPTY 45 | 46 | override fun createParser(project: Project?): PsiParser = FeatureFlagParser() 47 | 48 | override fun getFileNodeType(): IFileElementType = FILE_ELEMENT_TYPE 49 | 50 | override fun createFile(viewProvider: FileViewProvider): PsiFile = 51 | FeatureFlagFile(viewProvider) 52 | 53 | override fun createElement(node: ASTNode?): PsiElement = 54 | FeatureFlagTypes.Factory.createElement(node) 55 | 56 | companion object { 57 | private val FILE_ELEMENT_TYPE: IFileElementType = IFileElementType(FeatureFlagLanguage) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/psi/FeatureFlagElementType.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin.psi 18 | 19 | import com.intellij.psi.tree.IElementType 20 | import com.linecorp.android.featureflag.ij.plugin.FeatureFlagLanguage 21 | 22 | /** 23 | * A class representing the type of element used by Grammar-Kit. 24 | */ 25 | class FeatureFlagElementType(debugName: String) : IElementType(debugName, FeatureFlagLanguage) 26 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/psi/FeatureFlagFile.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin.psi 18 | 19 | import com.intellij.extapi.psi.PsiFileBase 20 | import com.intellij.psi.FileViewProvider 21 | import com.linecorp.android.featureflag.ij.plugin.FeatureFlagFileType 22 | import com.linecorp.android.featureflag.ij.plugin.FeatureFlagLanguage 23 | 24 | /** 25 | * A class which represents a feature flag file. 26 | */ 27 | class FeatureFlagFile(viewProvider: FileViewProvider) : 28 | PsiFileBase(viewProvider, FeatureFlagLanguage) { 29 | 30 | override fun getFileType() = FeatureFlagFileType 31 | 32 | override fun toString() = "FeatureFlag File" 33 | } 34 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/psi/FeatureFlagModifierType.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin.psi 18 | 19 | /** 20 | * A list of modifier types for feature flags. 21 | */ 22 | enum class FeatureFlagModifierType { 23 | OVERRIDABLE, PRIVATE, LITERALIZE 24 | } 25 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/psi/FeatureFlagNamedElement.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin.psi 18 | 19 | import com.intellij.navigation.NavigationItem 20 | import com.intellij.psi.PsiNameIdentifierOwner 21 | 22 | /** 23 | * An interface for feature flag for [PsiNameIdentifierOwner] to indicate that it is a named 24 | * element. 25 | */ 26 | interface FeatureFlagNamedElement : PsiNameIdentifierOwner, NavigationItem 27 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/psi/FeatureFlagTokenSets.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin.psi 18 | 19 | import com.intellij.psi.tree.TokenSet 20 | 21 | /** 22 | * A collection of [TokenSet] for the feature flag language. 23 | */ 24 | object FeatureFlagTokenSets { 25 | var COMMENTS: TokenSet = TokenSet.create(FeatureFlagTypes.COMMENT) 26 | } 27 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/psi/FeatureFlagTokenType.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin.psi 18 | 19 | import com.intellij.psi.tree.IElementType 20 | import com.linecorp.android.featureflag.ij.plugin.FeatureFlagLanguage 21 | 22 | /** 23 | * A class representing the type of token used by Grammar-Kit. 24 | */ 25 | class FeatureFlagTokenType(debugName: String) : IElementType(debugName, FeatureFlagLanguage) { 26 | override fun toString(): String = "FeatureFlagTokenType." + super.toString() 27 | } 28 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/psi/impl/FeatureFlagNamedElementImpl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin.psi.impl 18 | 19 | import com.intellij.extapi.psi.ASTWrapperPsiElement 20 | import com.intellij.lang.ASTNode 21 | import com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagNamedElement 22 | 23 | /** 24 | * An implementation of [FeatureFlagNamedElement]. 25 | */ 26 | abstract class FeatureFlagNamedElementImpl( 27 | node: ASTNode 28 | ) : ASTWrapperPsiElement(node), FeatureFlagNamedElement { 29 | 30 | override fun getTextOffset(): Int = nameIdentifier?.textOffset ?: super.getTextOffset() 31 | } 32 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/psi/impl/FeatureFlagPsiImplUtil.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin.psi.impl 18 | 19 | import com.intellij.navigation.ItemPresentation 20 | import com.intellij.psi.PsiElement 21 | import com.intellij.psi.PsiFileFactory 22 | import com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagFile 23 | import com.linecorp.android.featureflag.ij.plugin.FeatureFlagFileType 24 | import com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagModifier 25 | import com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagModifierType 26 | import com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagProperty 27 | import com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagTypes 28 | import javax.swing.Icon 29 | 30 | /** 31 | * A class for storing functions used in PSI Element classes generated by Grammar-Kit. 32 | */ 33 | object FeatureFlagPsiImplUtil { 34 | 35 | @JvmStatic 36 | fun getKey(element: FeatureFlagProperty): String? = 37 | element.node.findChildByType(FeatureFlagTypes.KEY)?.text 38 | 39 | @JvmStatic 40 | fun getName(element: FeatureFlagProperty): String? = getKey(element) 41 | 42 | @JvmStatic 43 | fun setName(element: FeatureFlagProperty, newName: String): PsiElement { 44 | val keyNode = element.node.findChildByType(FeatureFlagTypes.KEY) ?: return element 45 | val newKeyNode = PsiFileFactory.getInstance(element.project) 46 | .createFeatureFlagFile(newName) 47 | .firstChild 48 | .firstChild 49 | .node 50 | element.node.replaceChild(keyNode, newKeyNode) 51 | return element 52 | } 53 | 54 | @JvmStatic 55 | fun getNameIdentifier(element: FeatureFlagProperty): PsiElement? = 56 | element.node.findChildByType(FeatureFlagTypes.KEY)?.psi 57 | 58 | @JvmStatic 59 | fun getPresentation(element: FeatureFlagProperty): ItemPresentation = 60 | object : ItemPresentation { 61 | override fun getPresentableText(): String? = element.getKey() 62 | 63 | override fun getLocationString(): String? = element.containingFile?.name 64 | 65 | override fun getIcon(unused: Boolean): Icon? = element.getIcon(0) 66 | } 67 | 68 | @JvmStatic 69 | fun getModifierType(element: FeatureFlagModifier): FeatureFlagModifierType? = when { 70 | element.node.findChildByType(FeatureFlagTypes.MODIFIER_OVERRIDABLE) != null -> 71 | FeatureFlagModifierType.OVERRIDABLE 72 | 73 | element.node.findChildByType(FeatureFlagTypes.MODIFIER_PRIVATE) != null -> 74 | FeatureFlagModifierType.PRIVATE 75 | 76 | element.node.findChildByType(FeatureFlagTypes.MODIFIER_LITERALIZE) != null -> 77 | FeatureFlagModifierType.LITERALIZE 78 | 79 | else -> null 80 | } 81 | 82 | private fun PsiFileFactory.createFeatureFlagFile(name: String): FeatureFlagFile = 83 | createFileFromText("FEATURE_FLAG", FeatureFlagFileType, "$name = OFF") as FeatureFlagFile 84 | } 85 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/structureview/FeatureFlagFileStructureViewElement.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin.structureview 18 | 19 | import com.intellij.ide.projectView.PresentationData 20 | import com.intellij.ide.structureView.StructureViewTreeElement 21 | import com.intellij.ide.util.treeView.smartTree.TreeElement 22 | import com.intellij.navigation.ItemPresentation 23 | import com.intellij.psi.PsiFile 24 | import com.intellij.psi.util.PsiTreeUtil 25 | import com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagProperty 26 | 27 | /** 28 | * A data representing the structure of the feature flag file for the structure view. 29 | */ 30 | class FeatureFlagFileStructureViewElement( 31 | private val psiElement: PsiFile 32 | ) : StructureViewTreeElement { 33 | 34 | override fun getValue(): Any = psiElement 35 | 36 | override fun navigate(requestFocus: Boolean) = psiElement.navigate(requestFocus) 37 | 38 | override fun canNavigate(): Boolean = psiElement.canNavigate() 39 | 40 | override fun canNavigateToSource(): Boolean = psiElement.canNavigateToSource() 41 | 42 | override fun getPresentation(): ItemPresentation = 43 | psiElement.presentation ?: PresentationData() 44 | 45 | override fun getChildren(): Array { 46 | val properties = 47 | PsiTreeUtil.getChildrenOfTypeAsList(psiElement, FeatureFlagProperty::class.java) 48 | return Array(properties.size) { FeatureFlagPropertyStructureViewElement(properties[it]) } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/structureview/FeatureFlagPropertyStructureViewElement.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin.structureview 18 | 19 | import com.intellij.ide.structureView.StructureViewTreeElement 20 | import com.intellij.ide.util.treeView.smartTree.TreeElement 21 | import com.intellij.navigation.ItemPresentation 22 | import com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagProperty 23 | 24 | /** 25 | * A data representing a single property of feature flag for the structure view. 26 | */ 27 | class FeatureFlagPropertyStructureViewElement( 28 | private val psiElement: FeatureFlagProperty 29 | ) : StructureViewTreeElement { 30 | 31 | override fun getValue(): Any = psiElement 32 | 33 | override fun navigate(requestFocus: Boolean) = psiElement.navigate(requestFocus) 34 | 35 | override fun canNavigate(): Boolean = psiElement.canNavigate() 36 | 37 | override fun canNavigateToSource(): Boolean = psiElement.canNavigateToSource() 38 | 39 | fun getModifierSortKey(): Int { 40 | val modifier = psiElement.modifier?.modifierType 41 | return modifier?.ordinal ?: -1 42 | } 43 | 44 | override fun getPresentation(): ItemPresentation = 45 | FeatureFlagPropertyStructureViewPresentation( 46 | psiElement.key.orEmpty(), 47 | psiElement.modifier?.modifierType 48 | ) 49 | 50 | override fun getChildren(): Array = emptyArray() 51 | } 52 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/structureview/FeatureFlagPropertyStructureViewPresentation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin.structureview 18 | 19 | import com.intellij.ide.projectView.PresentationData 20 | import com.intellij.ui.RowIcon 21 | import com.intellij.util.ui.EmptyIcon 22 | import com.linecorp.android.featureflag.ij.plugin.icon.FeatureFlagIcons 23 | import com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagModifierType 24 | import javax.swing.Icon 25 | 26 | /** 27 | * A presentation data of single property in structure view. 28 | */ 29 | class FeatureFlagPropertyStructureViewPresentation( 30 | private val flagName: String, 31 | private val modifier: FeatureFlagModifierType? 32 | ) : PresentationData() { 33 | 34 | override fun getPresentableText(): String = flagName 35 | 36 | override fun getIcon(open: Boolean): Icon { 37 | val modifierIcon = when (modifier) { 38 | FeatureFlagModifierType.OVERRIDABLE -> FeatureFlagIcons.STRUCTURE_VIEW_OVERRIDABLE 39 | FeatureFlagModifierType.PRIVATE -> FeatureFlagIcons.STRUCTURE_VIEW_PRIVATE 40 | FeatureFlagModifierType.LITERALIZE -> FeatureFlagIcons.STRUCTURE_VIEW_LITERALIZE 41 | null -> EmptyIcon.ICON_16 42 | } 43 | return RowIcon(FeatureFlagIcons.SINGLE_PROPERTY, modifierIcon) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/structureview/FeatureFlagStructureViewFactory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin.structureview 18 | 19 | import com.intellij.ide.structureView.StructureViewBuilder 20 | import com.intellij.ide.structureView.StructureViewModel 21 | import com.intellij.ide.structureView.TreeBasedStructureViewBuilder 22 | import com.intellij.lang.PsiStructureViewFactory 23 | import com.intellij.openapi.editor.Editor 24 | import com.intellij.psi.PsiFile 25 | 26 | /** 27 | * An implementation of [PsiStructureViewFactory] to provide structure view for feature flag file. 28 | */ 29 | class FeatureFlagStructureViewFactory : PsiStructureViewFactory { 30 | 31 | override fun getStructureViewBuilder(psiFile: PsiFile): StructureViewBuilder = 32 | FeatureFlagStructureViewBuilder(psiFile) 33 | 34 | /** 35 | * An implementation of [TreeBasedStructureViewBuilder] to provide structure view model for 36 | * feature flag file. 37 | */ 38 | private class FeatureFlagStructureViewBuilder( 39 | private val psiFile: PsiFile 40 | ) : TreeBasedStructureViewBuilder() { 41 | override fun createStructureViewModel(editor: Editor?): StructureViewModel = 42 | FeatureFlagStructureViewModel(editor, psiFile) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/structureview/FeatureFlagStructureViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin.structureview 18 | 19 | import com.intellij.icons.AllIcons 20 | import com.intellij.ide.structureView.StructureViewModel 21 | import com.intellij.ide.structureView.StructureViewModelBase 22 | import com.intellij.ide.structureView.StructureViewTreeElement 23 | import com.intellij.ide.util.treeView.smartTree.ActionPresentation 24 | import com.intellij.ide.util.treeView.smartTree.ActionPresentationData 25 | import com.intellij.ide.util.treeView.smartTree.Sorter 26 | import com.intellij.openapi.editor.Editor 27 | import com.intellij.psi.PsiFile 28 | import com.linecorp.android.featureflag.ij.plugin.FeatureFlagBundle 29 | import com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagProperty 30 | 31 | /** 32 | * A view model for the structure view of the feature flag file. 33 | */ 34 | class FeatureFlagStructureViewModel( 35 | editor: Editor?, 36 | psiFile: PsiFile 37 | ) : StructureViewModelBase(psiFile, editor, FeatureFlagFileStructureViewElement(psiFile)), 38 | StructureViewModel.ElementInfoProvider { 39 | 40 | override fun getSorters(): Array = arrayOf(Sorter.ALPHA_SORTER, ModifierSorter) 41 | 42 | override fun isAlwaysShowsPlus(element: StructureViewTreeElement?): Boolean = false 43 | 44 | override fun isAlwaysLeaf(element: StructureViewTreeElement): Boolean = 45 | element is FeatureFlagPropertyStructureViewElement 46 | 47 | override fun getSuitableClasses(): Array> = arrayOf(FeatureFlagProperty::class.java) 48 | 49 | /** 50 | * A sorter for [FeatureFlagPropertyStructureViewElement]. 51 | */ 52 | private object ModifierSorter : Sorter { 53 | 54 | private val PRESENTATION = ActionPresentationData( 55 | FeatureFlagBundle.message("structureView.modifier"), 56 | null, 57 | AllIcons.ObjectBrowser.Sorted 58 | ) 59 | 60 | override fun getComparator(): Comparator<*> = ModifierComparator 61 | 62 | override fun isVisible(): Boolean = true 63 | 64 | override fun toString(): String = name 65 | 66 | override fun getPresentation(): ActionPresentation = PRESENTATION 67 | 68 | override fun getName(): String = "FEATURE_FLAG_MODIFIER_COMPARATOR" 69 | 70 | /** 71 | * A comparator for [FeatureFlagPropertyStructureViewElement]. 72 | */ 73 | private object ModifierComparator : Comparator { 74 | override fun compare(left: Any, right: Any): Int { 75 | val leftModifier = 76 | (left as? FeatureFlagPropertyStructureViewElement)?.getModifierSortKey() ?: -1 77 | val rightModifier = 78 | (right as? FeatureFlagPropertyStructureViewElement)?.getModifierSortKey() ?: -1 79 | return leftModifier.compareTo(rightModifier) 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/syntaxhighlight/FeatureFlagSyntaxHighlighter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin.syntaxhighlight 18 | 19 | import com.intellij.lexer.Lexer 20 | import com.intellij.openapi.editor.HighlighterColors 21 | import com.intellij.openapi.editor.colors.TextAttributesKey 22 | import com.intellij.openapi.editor.colors.TextAttributesKey.createTextAttributesKey 23 | import com.intellij.openapi.fileTypes.SyntaxHighlighterBase 24 | import com.intellij.psi.TokenType 25 | import com.intellij.psi.tree.IElementType 26 | import com.linecorp.android.featureflag.ij.plugin.lexer.FeatureFlagLexer 27 | import com.linecorp.android.featureflag.ij.plugin.psi.FeatureFlagTypes 28 | import com.intellij.openapi.editor.DefaultLanguageHighlighterColors as DefaultHighlighterColors 29 | 30 | /** 31 | * A syntax highlighter for the feature flag language. 32 | */ 33 | class FeatureFlagSyntaxHighlighter : SyntaxHighlighterBase() { 34 | 35 | override fun getHighlightingLexer(): Lexer = FeatureFlagLexer() 36 | 37 | override fun getTokenHighlights(tokenType: IElementType): Array = 38 | when (tokenType) { 39 | FeatureFlagTypes.SEPARATOR -> SEPARATOR_KEYS 40 | FeatureFlagTypes.KEY -> KEY_KEYS 41 | FeatureFlagTypes.VALUE_STRING -> VALUE_STRING_KEYS 42 | FeatureFlagTypes.VALUE_VERSION -> VALUE_VERSION_KEYS 43 | FeatureFlagTypes.VALUE_USERNAME -> VALUE_USERNAME_KEYS 44 | FeatureFlagTypes.VALUE_REFERENCE_PACKAGE -> VALUE_REFERENCE_PACKAGE_KEYS 45 | FeatureFlagTypes.VALUE_REFERENCE_NAME -> VALUE_REFERENCE_NAME_KEYS 46 | FeatureFlagTypes.COMMENT -> COMMENT_KEYS 47 | FeatureFlagTypes.MODIFIER_OVERRIDABLE, 48 | FeatureFlagTypes.MODIFIER_PRIVATE, 49 | FeatureFlagTypes.MODIFIER_LITERALIZE -> MODIFIER_KEYS 50 | 51 | FeatureFlagTypes.COMMA -> COMMA_KEYS 52 | FeatureFlagTypes.AND -> AND_KEYS 53 | FeatureFlagTypes.COLON -> COLON_KEYS 54 | TokenType.BAD_CHARACTER -> BAD_CHAR_KEYS 55 | else -> EMPTY_KEYS 56 | } 57 | 58 | companion object { 59 | private val BAD_CHAR_KEYS: Array = 60 | textAttributesKeysOf("FeatureFlag_BAD_CHARACTER" to HighlighterColors.BAD_CHARACTER) 61 | 62 | private val SEPARATOR_KEYS: Array = 63 | textAttributesKeysOf("FeatureFlag_SEPARATOR" to DefaultHighlighterColors.OPERATION_SIGN) 64 | 65 | private val KEY_KEYS: Array = 66 | textAttributesKeysOf("FeatureFlag_KEY" to DefaultHighlighterColors.IDENTIFIER) 67 | 68 | private val VALUE_STRING_KEYS: Array = 69 | textAttributesKeysOf("FeatureFlag_VALUE_STRING" to DefaultHighlighterColors.STRING) 70 | 71 | private val VALUE_VERSION_KEYS: Array = 72 | textAttributesKeysOf("FeatureFlag_VALUE_VERSION" to DefaultHighlighterColors.NUMBER) 73 | 74 | private val VALUE_USERNAME_KEYS: Array = 75 | textAttributesKeysOf( 76 | "FeatureFlag_VALUE_USERNAME" to DefaultHighlighterColors.INSTANCE_FIELD 77 | ) 78 | 79 | private val VALUE_REFERENCE_PACKAGE_KEYS: Array = 80 | textAttributesKeysOf( 81 | "FeatureFlag_VALUE_REFERENCE_PACKAGE" to DefaultHighlighterColors.STATIC_FIELD 82 | ) 83 | 84 | private val VALUE_REFERENCE_NAME_KEYS: Array = 85 | textAttributesKeysOf( 86 | "FeatureFlag_VALUE_REFERENCE_VALUE" to DefaultHighlighterColors.STRING 87 | ) 88 | 89 | private val COMMENT_KEYS: Array = 90 | textAttributesKeysOf("FeatureFlag_COMMENT" to DefaultHighlighterColors.LINE_COMMENT) 91 | 92 | private val MODIFIER_KEYS: Array = 93 | textAttributesKeysOf("FeatureFlag_MODIFIER" to DefaultHighlighterColors.KEYWORD) 94 | 95 | private val COMMA_KEYS: Array = 96 | textAttributesKeysOf("FeatureFlag_COMMA" to DefaultHighlighterColors.COMMA) 97 | 98 | private val AND_KEYS: Array = 99 | textAttributesKeysOf("FeatureFlag_AND" to DefaultHighlighterColors.MARKUP_TAG) 100 | 101 | private val COLON_KEYS: Array = 102 | textAttributesKeysOf("FeatureFlag_COLON" to DefaultHighlighterColors.MARKUP_TAG) 103 | 104 | private val EMPTY_KEYS: Array = emptyArray() 105 | 106 | private fun textAttributesKeysOf( 107 | vararg keys: Pair 108 | ): Array = 109 | Array(keys.size) { createTextAttributesKey(keys[it].first, keys[it].second) } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/kotlin/com/linecorp/android/featureflag/ij/plugin/syntaxhighlight/FeatureFlagSyntaxHighlighterFactory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 LY Corporation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linecorp.android.featureflag.ij.plugin.syntaxhighlight 18 | 19 | import com.intellij.openapi.fileTypes.SyntaxHighlighter 20 | import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory 21 | import com.intellij.openapi.project.Project 22 | import com.intellij.openapi.vfs.VirtualFile 23 | 24 | /** 25 | * An implementation of [SyntaxHighlighterFactory] for the feature flag language. 26 | */ 27 | class FeatureFlagSyntaxHighlighterFactory : SyntaxHighlighterFactory() { 28 | 29 | override fun getSyntaxHighlighter(project: Project?, file: VirtualFile?): SyntaxHighlighter = 30 | FeatureFlagSyntaxHighlighter() 31 | } 32 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/resources/FeatureFlagIconMappings.json: -------------------------------------------------------------------------------- 1 | { 2 | "icons": { 3 | "expui": { 4 | "featureFlagFile.svg": "icons/featureFlagFile.svg", 5 | "featureFlagSingleGutter.svg": "icons/featureFlagSingleGutter.svg", 6 | "featureFlagSingleProperty.svg": "icons/featureFlagSingleProperty.svg", 7 | "featureFlagStructureViewOverridable.svg": "icons/featureFlagStructureViewOverridable.svg", 8 | "featureFlagStructureViewLiteralize.svg": "icons/featureFlagStructureViewLiteralize.svg", 9 | "featureFlagStructureViewPrivate.svg": "icons/featureFlagStructureViewPrivate.svg" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | com.linecorp.android.featureflag.ij.plugin 3 | FeatureFlag Android Support 4 | LY Corporation 5 | 6 | com.intellij.modules.platform 7 | com.intellij.java 8 | org.jetbrains.kotlin 9 | 10 | messages.FeatureFlagBundle 11 | 12 | 13 | 14 | 15 | 16 | 17 | 25 | 26 | 27 | 28 | 31 | 32 | 35 | 36 | 39 | 40 | 43 | 44 | 46 | 47 | 49 | 50 | 53 | 54 | 58 | 59 | 63 | 64 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/resources/META-INF/pluginIcon_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/resources/icons/expui/featureFlagFile.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/resources/icons/expui/featureFlagFile_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/resources/icons/expui/featureFlagSingleGutter.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/resources/icons/expui/featureFlagSingleGutter_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/resources/icons/expui/featureFlagSingleProperty.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/resources/icons/expui/featureFlagSingleProperty_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/resources/icons/expui/featureFlagStructureViewLiteralize.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/resources/icons/expui/featureFlagStructureViewLiteralize_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/resources/icons/expui/featureFlagStructureViewOverridable.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/resources/icons/expui/featureFlagStructureViewOverridable_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/resources/icons/expui/featureFlagStructureViewPrivate.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/resources/icons/expui/featureFlagStructureViewPrivate_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/resources/icons/featureFlagFile.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/resources/icons/featureFlagFile_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/resources/icons/featureFlagSingleGutter.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/resources/icons/featureFlagSingleGutter_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/resources/icons/featureFlagSingleProperty.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/resources/icons/featureFlagSingleProperty_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/resources/icons/featureFlagStructureViewLiteralize.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/resources/icons/featureFlagStructureViewLiteralize_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/resources/icons/featureFlagStructureViewOverridable.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/resources/icons/featureFlagStructureViewPrivate.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /intellij-plugin/src/main/resources/messages/FeatureFlagBundle.properties: -------------------------------------------------------------------------------- 1 | documentation.valueInCurrentClasspath=Value in the current classpath 2 | documentation.notGeneratedYet=(Not generated yet) 3 | lineMarker.tooltip=Navigate to the feature flag definition 4 | structureView.modifier=Modifier 5 | -------------------------------------------------------------------------------- /intellij-plugin/src/test/kotlin/com/linecorp/android/featureflag/ij/plugin/FeatureFlagParsingTest.kt: -------------------------------------------------------------------------------- 1 | package com.linecorp.android.featureflag.ij.plugin 2 | 3 | import com.intellij.testFramework.ParsingTestCase 4 | import com.linecorp.android.featureflag.ij.plugin.parser.FeatureFlagParserDefinition 5 | 6 | /** 7 | * A test class for parsing feature flag files and generating PSI tree. 8 | */ 9 | class FeatureFlagParsingTest : ParsingTestCase("", "feature_flag", FeatureFlagParserDefinition()) { 10 | override fun getTestDataPath(): String = "src/test/resources/testData" 11 | override fun includeRanges(): Boolean = true 12 | fun testParsingTestData() = doTest(true) 13 | } 14 | -------------------------------------------------------------------------------- /intellij-plugin/src/test/resources/testData/ParsingTestData.feature_flag: -------------------------------------------------------------------------------- 1 | # Simple property 2 | FLAG_1 = DEBUG # Enabled when build in `DEBUG` phase. 3 | FLAG_2 = 1.2.0~ # Enabled when module version is `1.2.0` or later. 4 | FLAG_3 = @user # Enabled if the username is `user`. 5 | FLAG_4 = packageName:FLAG_A # Delegates flag enability to `FLAG_A` in module which has `packageName` as packageName property. 6 | 7 | # Property with options 8 | OVERRIDABLE FLAG_5 = DEBUG # Makes the flag modifiable at runtime. 9 | PRIVATE FLAG_6 = DEBUG # Makes the flag not accessible from a flag property file in another module. 10 | LITERALIZE FLAG_7 = DEBUG # Try to use a primitive boolean as the flag value. 11 | 12 | # Property combination 13 | # Enabled if either of the following conditions satisfies 14 | # 1. Built in `DEBUG` phase. 15 | # 2. Built in `RELEASE` phase and version `1.3.0` or later. 16 | FLAG_8 = DEBUG, RELEASE & 1.3.0~ 17 | 18 | PRIVATE FLAG_9_USERS = @user1, @user2 # Enabled if built by `user1` or `user2` 19 | FLAG_9 = FLAG_9_USERS & DEBUG # Enabled if `FLAG_9_USERS` is enabled and built in `DEBUG` phase. 20 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | versionCatalogs { 3 | } 4 | } 5 | 6 | rootProject.name = "feature-flag-plugin" 7 | 8 | include(":feature-flag") 9 | --------------------------------------------------------------------------------