├── docs ├── README.md ├── adr │ ├── README.md │ └── 0001-git-hooks.md └── guarding-process.svg ├── guarding.guarding ├── guarding_core ├── cbindgen.toml ├── src │ ├── lib.rs │ ├── rule_executor │ │ ├── mod.rs │ │ ├── rule_error.rs │ │ ├── package_matcher.rs │ │ └── executor.rs │ └── domain │ │ ├── code_import.rs │ │ ├── code_package.rs │ │ ├── code_module.rs │ │ ├── code_annotation.rs │ │ ├── code_file.rs │ │ ├── code_function.rs │ │ ├── mod.rs │ │ └── code_class.rs ├── guarding_core.h ├── README.md └── Cargo.toml ├── guarding_ident ├── README.md ├── src │ ├── identify │ │ ├── mod.rs │ │ ├── code_ident.rs │ │ ├── js_ident.rs │ │ ├── c_sharp_ident.rs │ │ ├── rust_ident.rs │ │ └── java_ident.rs │ ├── lib.rs │ └── model_builder.rs └── Cargo.toml ├── .adr.json ├── .gitignore ├── _fixtures └── java │ ├── src │ └── main │ │ └── java │ │ └── com │ │ └── phodal │ │ └── pepper │ │ ├── refactor │ │ ├── parser │ │ │ ├── README.md │ │ │ ├── BaseParser.java │ │ │ ├── JsonParser.java │ │ │ └── XmlParser.java │ │ ├── switchcases │ │ │ ├── clz │ │ │ │ ├── CaseInterface.java │ │ │ │ ├── CaseA.java │ │ │ │ └── CaseB.java │ │ │ ├── README.md │ │ │ ├── RegisterPattern.java │ │ │ └── RegisterUsecase.java │ │ └── staticclass │ │ │ ├── FileVerifyFactory.java │ │ │ ├── FileHandler.java │ │ │ ├── FileVerifyFactoryImpl.java │ │ │ ├── FileVerify.java │ │ │ └── LogFileHandle.java │ │ ├── powermock │ │ ├── MockNewClass.java │ │ ├── MockCalenderInstance.java │ │ ├── ClassCallStaticMethodObj.java │ │ ├── FinalClass.java │ │ ├── StaticMethod.java │ │ ├── SpyMockEmployeeService.java │ │ ├── PrivateMethodDemo.java │ │ └── SystemClassUser.java │ │ ├── normal │ │ ├── date │ │ │ └── mockito │ │ │ │ ├── DateTimeImpl.java │ │ │ │ └── MyDateClass.java │ │ ├── file │ │ │ └── NewFileExample.java │ │ └── exception │ │ │ └── MyDictionary.java │ │ └── PepperApplication.java │ └── size.guarding ├── guarding_parser ├── README.md ├── src │ ├── support │ │ ├── mod.rs │ │ ├── package_unify.rs │ │ └── str_support.rs │ ├── validator.rs │ ├── lib.rs │ ├── errors.rs │ ├── guarding.pest │ ├── ast.rs │ └── parser.rs └── Cargo.toml ├── guarding_adapter ├── cbindgen.toml ├── guarding_adapter.h ├── README.md ├── .gitignore ├── Cargo.toml └── src │ └── lib.rs ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── workflows │ ├── build.yml │ └── release.yml └── CONTRIBUTING.md ├── benches └── my_benchmark.rs ├── Makefile ├── src ├── bin │ ├── ident.rs │ └── guarding.rs ├── lib.rs └── tests.rs ├── examples ├── guard.keywords └── 0.0.1.sample ├── LICENSE ├── Cargo.toml ├── README.md └── Cargo.lock /docs/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /guarding.guarding: -------------------------------------------------------------------------------- 1 | // path: src/* 2 | -------------------------------------------------------------------------------- /guarding_core/cbindgen.toml: -------------------------------------------------------------------------------- 1 | language = "C" 2 | -------------------------------------------------------------------------------- /guarding_ident/README.md: -------------------------------------------------------------------------------- 1 | # Guarding Identifier 2 | 3 | -------------------------------------------------------------------------------- /.adr.json: -------------------------------------------------------------------------------- 1 | {"language":"en","path":"docs/adr/","prefix":"","digits":4} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea 3 | guard.json 4 | guard-ident.json 5 | -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/refactor/parser/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /guarding_core/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod rule_executor; 2 | pub mod domain; 3 | -------------------------------------------------------------------------------- /guarding_parser/README.md: -------------------------------------------------------------------------------- 1 | # Guarding Parser 2 | 3 | usage: 4 | 5 | ``` 6 | 7 | ``` -------------------------------------------------------------------------------- /guarding_parser/src/support/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod str_support; 2 | pub mod package_unify; 3 | -------------------------------------------------------------------------------- /docs/adr/README.md: -------------------------------------------------------------------------------- 1 | # Architecture Decision Records 2 | 3 | * [1. git-hooks](0001-git-hooks.md) 4 | -------------------------------------------------------------------------------- /_fixtures/java/size.guarding: -------------------------------------------------------------------------------- 1 | package(".")::file.len should < 200; 2 | package(".")::file.len should > 50; 3 | -------------------------------------------------------------------------------- /guarding_adapter/cbindgen.toml: -------------------------------------------------------------------------------- 1 | language = "C" 2 | 3 | [parse] 4 | parse_deps = true 5 | include = ["guarding_core"] 6 | -------------------------------------------------------------------------------- /guarding_core/guarding_core.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | -------------------------------------------------------------------------------- /guarding_ident/src/identify/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod code_ident; 2 | pub mod js_ident; 3 | pub mod java_ident; 4 | pub mod rust_ident; 5 | pub mod c_sharp_ident; 6 | -------------------------------------------------------------------------------- /guarding_core/README.md: -------------------------------------------------------------------------------- 1 | # Guarding Core 2 | 3 | CBindgen 4 | 5 | ```bash 6 | cbindgen --config cbindgen.toml --crate guarding_core --output guarding_core.h 7 | ``` 8 | -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/refactor/parser/BaseParser.java: -------------------------------------------------------------------------------- 1 | package com.phodal.pepper.refactor.parser; 2 | 3 | public interface BaseParser { 4 | void parse(); 5 | } 6 | -------------------------------------------------------------------------------- /guarding_core/src/rule_executor/mod.rs: -------------------------------------------------------------------------------- 1 | pub use executor::RuleExecutor; 2 | pub use rule_error::RuleErrorMsg; 3 | 4 | pub mod executor; 5 | pub mod package_matcher; 6 | pub mod rule_error; 7 | -------------------------------------------------------------------------------- /guarding_parser/src/validator.rs: -------------------------------------------------------------------------------- 1 | // todo: add validator 2 | // pub fn validate_ast<'a, 'i: 'a>(rules: Pairs) -> Vec> { 3 | // let vec = vec![]; 4 | // 5 | // vec 6 | // } -------------------------------------------------------------------------------- /guarding_adapter/guarding_adapter.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | char *from_string(const char *models, const char *rules); 7 | -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/refactor/switchcases/clz/CaseInterface.java: -------------------------------------------------------------------------------- 1 | package com.phodal.pepper.refactor.switchcases.clz; 2 | 3 | public interface CaseInterface { 4 | public void buildMap(); 5 | } 6 | -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/refactor/staticclass/FileVerifyFactory.java: -------------------------------------------------------------------------------- 1 | package com.phodal.pepper.refactor.staticclass; 2 | 3 | public interface FileVerifyFactory { 4 | FileVerify genFileVerify(); 5 | } 6 | -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/refactor/switchcases/README.md: -------------------------------------------------------------------------------- 1 | google common reflect 2 | 3 | - https://github.com/google/guava/tree/master/guava/src/com/google/common/reflect 4 | - https://github.com/ronmamo/reflections 5 | -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/refactor/parser/JsonParser.java: -------------------------------------------------------------------------------- 1 | package com.phodal.pepper.refactor.parser; 2 | 3 | public class JsonParser implements BaseParser { 4 | @Override 5 | public void parse() { 6 | 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/refactor/parser/XmlParser.java: -------------------------------------------------------------------------------- 1 | package com.phodal.pepper.refactor.parser; 2 | 3 | public class XmlParser implements BaseParser { 4 | @Override 5 | public void parse() { 6 | 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/refactor/staticclass/FileHandler.java: -------------------------------------------------------------------------------- 1 | package com.phodal.pepper.refactor.staticclass; 2 | 3 | public class FileHandler { 4 | public static void verify(String routerName) { 5 | // do something 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /guarding_parser/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate pest; 2 | #[macro_use] 3 | extern crate pest_derive; 4 | extern crate serde; 5 | 6 | pub use parser::parse; 7 | 8 | pub mod ast; 9 | pub mod validator; 10 | pub mod parser; 11 | pub mod errors; 12 | pub mod support; 13 | -------------------------------------------------------------------------------- /guarding_core/src/domain/code_import.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[repr(C)] 4 | #[derive(Serialize, Deserialize, Debug, Clone)] 5 | pub struct CodeImport { 6 | pub name: String, 7 | pub import: String, 8 | pub source: String, 9 | } 10 | -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/powermock/MockNewClass.java: -------------------------------------------------------------------------------- 1 | package com.phodal.pepper.powermock; 2 | 3 | import java.awt.*; 4 | 5 | public class MockNewClass { 6 | public Point publicMethod() { 7 | return new Point(11, 11); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /guarding_ident/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod identify; 2 | pub mod model_builder; 3 | 4 | pub use model_builder::ModelBuilder; 5 | 6 | pub use identify::code_ident; 7 | pub use identify::java_ident; 8 | pub use identify::js_ident; 9 | pub use identify::rust_ident; 10 | pub use identify::c_sharp_ident; 11 | -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/refactor/staticclass/FileVerifyFactoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.phodal.pepper.refactor.staticclass; 2 | 3 | class FileVerifyFactoryImpl implements FileVerifyFactory { 4 | @Override 5 | public FileVerify genFileVerify() { 6 | return new FileVerify(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/refactor/staticclass/FileVerify.java: -------------------------------------------------------------------------------- 1 | package com.phodal.pepper.refactor.staticclass; 2 | 3 | import java.util.Map; 4 | 5 | public class FileVerify { 6 | void getVerify(Map.Entry entry) { 7 | FileHandler.verify(entry.getValue()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /guarding_core/src/domain/code_package.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::domain::code_class::CodeClass; 4 | 5 | #[repr(C)] 6 | #[derive(Serialize, Deserialize, Debug, Clone)] 7 | pub struct CodePackage { 8 | pub name: String, 9 | pub path: String, 10 | pub class: Vec, 11 | } 12 | -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/powermock/MockCalenderInstance.java: -------------------------------------------------------------------------------- 1 | package com.phodal.pepper.powermock; 2 | 3 | import java.util.Calendar; 4 | import java.util.Date; 5 | 6 | public class MockCalenderInstance { 7 | public Date getDate() { 8 | return Calendar.getInstance().getTime(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/powermock/ClassCallStaticMethodObj.java: -------------------------------------------------------------------------------- 1 | package com.phodal.pepper.powermock; 2 | 3 | public class ClassCallStaticMethodObj { 4 | public void execute() { 5 | boolean foo = StaticMethod.firstStaticMethod("2"); 6 | int bar = StaticMethod.secondStaticMethod(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /guarding_adapter/README.md: -------------------------------------------------------------------------------- 1 | # Guarding Adapter 2 | 3 | ## Usage 4 | 5 | 6 | 7 | ## Dev Setup 8 | 9 | 1. install CBindgen 10 | 11 | ```bash 12 | cargo install --force cbindgen 13 | ``` 14 | 15 | 2. generate header 16 | 17 | ``` 18 | cbindgen --config cbindgen.toml --crate guarding_adapter --output guarding_adapter.h 19 | ``` 20 | -------------------------------------------------------------------------------- /guarding_core/src/domain/code_module.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::domain::code_package::CodePackage; 4 | 5 | #[repr(C)] 6 | #[derive(Serialize, Deserialize, Debug, Clone)] 7 | pub struct CodeModule { 8 | pub name: String, 9 | pub path: String, 10 | pub package: Vec, 11 | } 12 | -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/powermock/FinalClass.java: -------------------------------------------------------------------------------- 1 | package com.phodal.pepper.powermock; 2 | 3 | public final class FinalClass { 4 | private final String value; 5 | 6 | public FinalClass(String value) { 7 | this.value = value; 8 | } 9 | 10 | public String getValue() { 11 | return value; 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/normal/date/mockito/DateTimeImpl.java: -------------------------------------------------------------------------------- 1 | package com.phodal.pepper.normal.date.mockito; 2 | 3 | import java.util.Date; 4 | 5 | interface DateTime { 6 | Date getDate(); 7 | } 8 | 9 | class DateTimeImpl implements DateTime { 10 | @Override 11 | public Date getDate() { 12 | return new Date(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/powermock/StaticMethod.java: -------------------------------------------------------------------------------- 1 | package com.phodal.pepper.powermock; 2 | 3 | public class StaticMethod { 4 | 5 | public static boolean firstStaticMethod(String param) { 6 | return true; 7 | } 8 | 9 | public static int secondStaticMethod() { 10 | int value = 1299; 11 | return value; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /guarding_adapter/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐞 Bug report 3 | about: Create a report about something that is not working 4 | --- 5 | 6 | 7 | ### Describe the bug 8 | A clear and concise description of what the bug is. 9 | 10 | ### Steps to reproduce (please include code) 11 | 12 | 13 | ### Environment 14 | - coco version 15 | - Rust version 16 | - OS: [e.g. OSX 10.13.4, Windows 10] 17 | -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/normal/file/NewFileExample.java: -------------------------------------------------------------------------------- 1 | package com.phodal.pepper.normal.file; 2 | 3 | import java.io.File; 4 | 5 | public class NewFileExample { 6 | public void openFile(String fileName) { 7 | File f = new File(fileName); 8 | if (!f.exists()) { 9 | throw new RuntimeException("File not found!"); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /benches/my_benchmark.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | use guarding_ident::ModelBuilder; 3 | use std::path::PathBuf; 4 | 5 | fn criterion_benchmark(c: &mut Criterion) { 6 | c.bench_function("lambda", |b| b.iter(|| { 7 | ModelBuilder::build_models_by_dir(PathBuf::from(".")); 8 | })); 9 | } 10 | 11 | criterion_group!(benches, criterion_benchmark); 12 | criterion_main!(benches); -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/refactor/switchcases/clz/CaseA.java: -------------------------------------------------------------------------------- 1 | package com.phodal.pepper.refactor.switchcases.clz; 2 | 3 | import com.phodal.pepper.refactor.switchcases.RegisterPattern; 4 | 5 | @RegisterPattern(register = "CASEA") 6 | public class CaseA implements CaseInterface { 7 | 8 | @Override 9 | public void buildMap() { 10 | System.out.println("CaseA - buildMap"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/refactor/switchcases/clz/CaseB.java: -------------------------------------------------------------------------------- 1 | package com.phodal.pepper.refactor.switchcases.clz; 2 | 3 | import com.phodal.pepper.refactor.switchcases.RegisterPattern; 4 | 5 | @RegisterPattern(register = "CASEB") 6 | public class CaseB implements CaseInterface { 7 | 8 | @Override 9 | public void buildMap() { 10 | System.out.println("CaseB - buildMap"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/PepperApplication.java: -------------------------------------------------------------------------------- 1 | package com.phodal.pepper; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class PepperApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(PepperApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/normal/date/mockito/MyDateClass.java: -------------------------------------------------------------------------------- 1 | package com.phodal.pepper.normal.date.mockito; 2 | 3 | public class MyDateClass { 4 | private final DateTime dateTime; 5 | 6 | public MyDateClass(final DateTime dateTime) { 7 | this.dateTime = dateTime; 8 | } 9 | 10 | public long getDoubleTime() { 11 | return dateTime.getDate().getTime() * 2; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /guarding_core/src/domain/code_annotation.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[repr(C)] 4 | #[derive(Serialize, Deserialize, Debug, Clone)] 5 | pub struct CodeAnnotation { 6 | pub name: String, 7 | pub key_values: Vec 8 | } 9 | 10 | #[repr(C)] 11 | #[derive(Serialize, Deserialize, Debug, Clone)] 12 | pub struct AnnotationKeyValue { 13 | pub key: String, 14 | pub values: Vec 15 | } 16 | -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/refactor/switchcases/RegisterPattern.java: -------------------------------------------------------------------------------- 1 | package com.phodal.pepper.refactor.switchcases; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.CONSTRUCTOR, ElementType.TYPE}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface RegisterPattern { 11 | String register(); 12 | } 13 | -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/normal/exception/MyDictionary.java: -------------------------------------------------------------------------------- 1 | package com.phodal.pepper.normal.exception; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class MyDictionary { 7 | private Map wordMap = new HashMap<>(); 8 | 9 | public void add(String word, String meaning) { 10 | wordMap.put(word, meaning); 11 | } 12 | 13 | public String getMeaning(String word) { 14 | return wordMap.get(word); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /docs/adr/0001-git-hooks.md: -------------------------------------------------------------------------------- 1 | # 1. git hooks 2 | 3 | Date: 2021-05-13 4 | 5 | ## Status 6 | 7 | 2021-05-13 proposed 8 | 9 | ## Context 10 | 11 | Hook samples: 12 | 13 | 14 | 1. [https://github.com/swellaby/rusty-hook](https://github.com/swellaby/rusty-hook) 15 | 2. [https://github.com/rhysd/cargo-husky](https://github.com/rhysd/cargo-husky) 16 | 3. [https://github.com/paulollivier/git-hooks](https://github.com/paulollivier/git-hooks) 17 | 18 | ## Decision 19 | 20 | Decision here... 21 | 22 | ## Consequences 23 | 24 | Consequences here... 25 | -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/powermock/SpyMockEmployeeService.java: -------------------------------------------------------------------------------- 1 | package com.phodal.pepper.powermock; 2 | 3 | public class SpyMockEmployeeService { 4 | public void foo() { 5 | log(); 6 | } 7 | 8 | public void foo(String str) { 9 | log(); 10 | } 11 | 12 | public void log() { 13 | System.out.println("I am console log"); 14 | } 15 | 16 | public boolean exist(String name) { 17 | return checkExist(name); 18 | } 19 | 20 | private boolean checkExist(String name) { 21 | throw new UnsupportedOperationException(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | strategy: 8 | matrix: 9 | os: [macos-latest, ubuntu-latest, windows-latest] 10 | runs-on: ${{ matrix.os }} 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Checkout submodules 14 | shell: bash 15 | run: | 16 | git fetch --tags 17 | auth_header="$(git config --local --get http.https://github.com/.extraheader)" 18 | git submodule sync --recursive 19 | git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 20 | 21 | - name: Run unit tests 22 | run: ${{matrix.ENV_VARS}} cargo test 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: docs_check docs fmt_check fmt clippy_check clippy build test check clean 2 | 3 | docs_check: 4 | cargo doc --no-deps --document-private-items --all-features # TODO: docs check won't fail if there is warning, should be fixed later 5 | 6 | docs: 7 | cargo doc --no-deps --document-private-items --all-features --open 8 | 9 | fmt_check: 10 | cargo fmt --all -- --check 11 | 12 | fmt: 13 | cargo fmt --all 14 | 15 | clippy_check: 16 | cargo clippy --all-features --all-targets 17 | 18 | clippy: 19 | cargo clippy --all-features --all-targets --fix 20 | 21 | build: 22 | cargo build --all-features --all-targets 23 | 24 | test: 25 | cargo test --all-features 26 | 27 | check: fmt_check clippy_check docs_check build test 28 | 29 | clean: 30 | cargo clean 31 | -------------------------------------------------------------------------------- /src/bin/ident.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::path::PathBuf; 3 | 4 | use clap::{AppSettings, Clap}; 5 | 6 | use guarding_ident::ModelBuilder; 7 | 8 | #[derive(Clap)] 9 | #[clap(version = "1.0", author = "Inherd Group ")] 10 | #[clap(setting = AppSettings::ColoredHelp)] 11 | struct Opts { 12 | #[clap(short, long, default_value = ".")] 13 | path: String, 14 | 15 | #[clap(short, long, default_value = "guard-ident.json")] 16 | output: String, 17 | } 18 | 19 | fn main() { 20 | let opts: Opts = Opts::parse(); 21 | 22 | let code_dir = PathBuf::from(opts.path); 23 | 24 | let models = ModelBuilder::build_models_by_dir(code_dir); 25 | let content = serde_json::to_string_pretty(&models).unwrap(); 26 | let _ = fs::write(opts.output, content); 27 | } 28 | -------------------------------------------------------------------------------- /guarding_core/src/domain/code_file.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::domain::code_function::CodeFunction; 4 | use crate::domain::code_class::CodeClass; 5 | 6 | #[repr(C)] 7 | #[derive(Serialize, Deserialize, Debug, Clone)] 8 | pub struct CodeFile { 9 | pub file_name: String, 10 | pub path: String, 11 | pub package: String, 12 | pub imports: Vec, 13 | pub classes: Vec, 14 | pub functions: Vec, 15 | } 16 | 17 | impl Default for CodeFile { 18 | fn default() -> Self { 19 | CodeFile { 20 | file_name: "".to_string(), 21 | path: "".to_string(), 22 | package: "".to_string(), 23 | imports: vec![], 24 | classes: vec![], 25 | functions: vec![], 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/guard.keywords: -------------------------------------------------------------------------------- 1 | onion: 2 | domainModel: 3 | domainService: 4 | applicationService: 5 | adapter: 6 | 7 | clean: 8 | 9 | layer: 10 | name 11 | 12 | rules: 13 | "Presentation" accessedBy 14 | 15 | module: 16 | resideIn 17 | access: 18 | only 19 | 20 | package: 21 | dependOn 22 | 23 | class: 24 | implement 25 | annotation 26 | annotatedWith 27 | 28 | dependent -> resideIn 29 | 30 | public 31 | 32 | checks 33 | cycle 34 | 35 | not accessedBy 36 | definedBy 37 | dependOn 38 | dependBy 39 | 40 | expression: 41 | and 42 | or 43 | not 44 | equals 45 | only 46 | 47 | contains 48 | endingWith 49 | startingWith 50 | 51 | file: 52 | ending 53 | start 54 | contains 55 | matching (regex) 56 | 57 | ignore: 58 | "" -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/powermock/PrivateMethodDemo.java: -------------------------------------------------------------------------------- 1 | package com.phodal.pepper.powermock; 2 | 3 | public class PrivateMethodDemo { 4 | public String say(String name) { 5 | return sayIt(name); 6 | } 7 | 8 | public String enhancedSay(String firstName, String lastName) { 9 | return sayIt(firstName, " ", lastName); 10 | } 11 | 12 | public String sayYear(String name, int years) { 13 | return doSayYear(years, name); 14 | } 15 | 16 | private String doSayYear(int years, String name) { 17 | return "Hello " + name + ", you are " + years + " old."; 18 | } 19 | 20 | private String sayIt(String firstName, String spacing, String lastName) { 21 | return "Hello" + firstName + spacing + lastName; 22 | } 23 | 24 | private String sayIt(String name) { 25 | return "Hello " + name; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F680 Feature Request" 3 | about: "I have a suggestion (and may want to implement it \U0001F642)!" 4 | title: '' 5 | labels: 'i: enhancement, i: needs triage' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Feature Request 11 | 12 | **Is your feature request related to a problem? Please describe.** 13 | A clear and concise description of what the problem is. Ex. I have an issue when [...] 14 | 15 | **Describe the solution you'd like** 16 | A clear and concise description of what you want to happen. Add any considered drawbacks. 17 | 18 | **Describe alternatives you've considered** 19 | A clear and concise description of any alternative solutions or features you've considered. 20 | 21 | **Teachability, Documentation, Adoption, Migration Strategy** 22 | If you can, explain how users will be able to use this and possibly write out a version the docs. 23 | Maybe a screenshot or design? 24 | -------------------------------------------------------------------------------- /src/bin/guarding.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::path::PathBuf; 3 | 4 | use clap::{AppSettings, Clap}; 5 | use guarding::exec_guarding; 6 | 7 | #[derive(Clap)] 8 | #[clap(version = "1.0", author = "Inherd Group ")] 9 | #[clap(setting = AppSettings::ColoredHelp)] 10 | struct Opts { 11 | #[clap(short, long, default_value = "guarding.guarding")] 12 | config: String, 13 | 14 | #[clap(short, long, default_value = "src")] 15 | path: String, 16 | 17 | #[clap(short, long, default_value = "guard.json")] 18 | output: String, 19 | } 20 | 21 | fn main() { 22 | let opts: Opts = Opts::parse(); 23 | 24 | let buf = PathBuf::from(opts.path); 25 | let conf = PathBuf::from(opts.config); 26 | let content = fs::read_to_string(conf).unwrap(); 27 | 28 | let errors = exec_guarding(content, buf); 29 | let content = serde_json::to_string_pretty(&errors).unwrap(); 30 | let _ = fs::write(opts.output, content); 31 | } 32 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate serde; 2 | 3 | use std::path::PathBuf; 4 | 5 | use guarding_core::domain::code_file::CodeFile; 6 | use guarding_ident::ModelBuilder; 7 | use guarding_core::rule_executor::{RuleErrorMsg, RuleExecutor}; 8 | use guarding_parser::ast::GuardRule; 9 | use guarding_parser::parser; 10 | 11 | pub fn exec_guarding(rule_content: String, code_dir: PathBuf) -> Vec { 12 | match parser::parse(rule_content.as_str()) { 13 | Err(e) => { 14 | println!("{}", e); 15 | vec![] 16 | }, 17 | Ok(rules) => { 18 | let models = ModelBuilder::build_models_by_dir(code_dir); 19 | exec(rules, models) 20 | } 21 | } 22 | } 23 | 24 | fn exec(rules: Vec, models: Vec) -> Vec { 25 | let mut executor = RuleExecutor::new(models, rules); 26 | executor.run(); 27 | 28 | return executor.errors; 29 | } 30 | 31 | 32 | #[cfg(test)] 33 | mod tests; 34 | -------------------------------------------------------------------------------- /guarding_parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "guarding_parser" 3 | version = "0.2.6" 4 | authors = ["Inherd Group "] 5 | edition = "2018" 6 | license = "MIT" 7 | readme = "README.md" 8 | repository = "https://github.com/inherd/guarding" 9 | documentation = "https://github.com/inherd/guarding" 10 | homepage = "https://github.com/inherd/guarding" 11 | description = """ 12 | Guarding is a guardians for code, architecture, layered. Guarding crate a architecture aguard DSL which based on ArchUnit. 13 | """ 14 | categories = ["text-processing", "command-line-interface", "development-tools"] 15 | exclude = [ 16 | ".github/*", 17 | ".gitattributes", 18 | ".adr.json", 19 | "guarding.guarding", 20 | "_fixtures", 21 | "docs", 22 | "examples", 23 | ] 24 | 25 | [dependencies] 26 | pest = "2.1.3" 27 | pest_derive = "2.1.0" 28 | 29 | # serialize 30 | serde = { version = "1.0", features = ["derive"] } 31 | serde_json = "1" 32 | 33 | [lib] 34 | name = "guarding_parser" 35 | crate-type = ["lib"] 36 | -------------------------------------------------------------------------------- /guarding_core/src/domain/code_function.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::domain::CodePoint; 4 | use crate::domain::Location; 5 | 6 | #[repr(C)] 7 | #[derive(Serialize, Deserialize, Debug, Clone)] 8 | pub struct CodeFunction { 9 | pub name: String, 10 | // todo: thinking in access 11 | pub vars: Vec, 12 | pub start: CodePoint, 13 | pub end: CodePoint 14 | } 15 | 16 | impl Default for CodeFunction { 17 | fn default() -> Self { 18 | CodeFunction { 19 | name: "".to_string(), 20 | vars: vec![], 21 | start: Default::default(), 22 | end: Default::default() 23 | } 24 | } 25 | } 26 | 27 | impl Location for CodeFunction { 28 | fn set_start(&mut self, row: usize, column: usize) { 29 | self.start.row = row; 30 | self.start.column = column; 31 | } 32 | 33 | fn set_end(&mut self, row: usize, column: usize) { 34 | self.end.row = row; 35 | self.end.column = column; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /guarding_core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "guarding_core" 3 | version = "0.2.7" 4 | authors = ["Inherd Group "] 5 | edition = "2018" 6 | license = "MIT" 7 | readme = "README.md" 8 | repository = "https://github.com/inherd/guarding" 9 | documentation = "https://github.com/inherd/guarding" 10 | homepage = "https://github.com/inherd/guarding" 11 | description = """ 12 | Guarding is a guardians for code, architecture, layered. Guarding crate a architecture aguard DSL which based on ArchUnit. 13 | """ 14 | categories = ["text-processing", "command-line-interface", "development-tools"] 15 | exclude = [ 16 | ".github/*", 17 | ".gitattributes", 18 | ".adr.json", 19 | "guarding.guarding", 20 | "_fixtures", 21 | "docs", 22 | "examples", 23 | ] 24 | 25 | [dependencies] 26 | # serialize 27 | serde = { version = "1.0", features = ["derive"] } 28 | serde_json = "1" 29 | 30 | regex = "1" 31 | 32 | guarding_parser = { path = "../guarding_parser", version = "0.2.6" } 33 | 34 | [lib] 35 | name = "guarding_core" 36 | crate-type = ["lib"] 37 | -------------------------------------------------------------------------------- /guarding_ident/src/identify/code_ident.rs: -------------------------------------------------------------------------------- 1 | use tree_sitter::{Node, QueryCapture}; 2 | 3 | use guarding_core::domain::code_function::CodeFunction; 4 | use guarding_core::domain::code_file::CodeFile; 5 | use guarding_core::domain::Location; 6 | 7 | pub trait CodeIdent { 8 | fn parse(code: &str) -> CodeFile; 9 | 10 | fn insert_location(model: &mut T, node: Node) { 11 | model.set_start(node.start_position().row, node.start_position().column); 12 | model.set_end(node.end_position().row, node.end_position().column); 13 | } 14 | 15 | fn create_function( capture: QueryCapture, text: &str) -> CodeFunction { 16 | let mut function = CodeFunction::default(); 17 | function.name = text.to_string(); 18 | 19 | let node = capture.node.parent().unwrap(); 20 | // RustIdent::insert_location(&mut function, node); 21 | 22 | function.set_start(node.start_position().row, node.start_position().column); 23 | function.set_end(node.end_position().row, node.end_position().column); 24 | 25 | function 26 | } 27 | } -------------------------------------------------------------------------------- /guarding_adapter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "guarding_adapter" 3 | version = "0.1.0" 4 | authors = ["Phodal Huang "] 5 | edition = "2018" 6 | license = "MIT" 7 | readme = "README.md" 8 | repository = "https://github.com/inherd/guarding" 9 | documentation = "https://github.com/inherd/guarding" 10 | homepage = "https://github.com/inherd/guarding" 11 | description = """ 12 | Guarding is a guardians for code, architecture, layered. Guarding crate a architecture aguard DSL which based on ArchUnit. 13 | """ 14 | categories = ["text-processing", "command-line-interface", "development-tools"] 15 | exclude = [ 16 | ".github/*", 17 | ".gitattributes", 18 | ".adr.json", 19 | "guarding.guarding", 20 | "_fixtures", 21 | "docs", 22 | "examples", 23 | ] 24 | 25 | [dependencies] 26 | 27 | guarding_parser = { path = "../guarding_parser", version = "0.2.5" } 28 | guarding_core = { path = "../guarding_core", version = "0.2.5" } 29 | 30 | serde = { version = "1.0", features = ["derive"] } 31 | serde_json = "1" 32 | 33 | [lib] 34 | name = "guarding_adapter" 35 | crate-type = ["lib", "cdylib"] 36 | -------------------------------------------------------------------------------- /guarding_core/src/domain/mod.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use code_class::CodeClass; 4 | 5 | pub mod code_import; 6 | pub mod code_file; 7 | pub mod code_package; 8 | pub mod code_module; 9 | pub mod code_class; 10 | pub mod code_function; 11 | pub mod code_annotation; 12 | 13 | impl Location for CodeClass { 14 | fn set_start(&mut self, row: usize, column: usize) { 15 | self.start.row = row; 16 | self.start.column = column; 17 | } 18 | 19 | fn set_end(&mut self, row: usize, column: usize) { 20 | self.end.row = row; 21 | self.end.column = column; 22 | } 23 | } 24 | 25 | pub trait Location { 26 | fn set_start(&mut self, row: usize, column: usize); 27 | fn set_end(&mut self, row: usize, column: usize); 28 | } 29 | 30 | #[repr(C)] 31 | #[derive(Serialize, Deserialize, Debug, Clone)] 32 | pub struct CodePoint { 33 | pub row: usize, 34 | pub column: usize 35 | } 36 | 37 | impl Default for CodePoint { 38 | fn default() -> Self { 39 | CodePoint { 40 | row: 0, 41 | column: 0 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /guarding_adapter/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{CStr, CString}; 2 | use std::os::raw::c_char; 3 | 4 | use guarding_core::domain::code_file::CodeFile; 5 | use guarding_core::rule_executor::{RuleExecutor}; 6 | use guarding_parser::parser; 7 | 8 | #[no_mangle] 9 | pub extern fn from_string(models: *const c_char, rules: *const c_char) -> *mut c_char { 10 | let rule_str = unsafe { 11 | assert!(!rules.is_null()); 12 | 13 | CStr::from_ptr(rules) 14 | }; 15 | 16 | let rules = rule_str.to_str().unwrap(); 17 | let guard_rules = parser::parse(rules).unwrap(); 18 | 19 | let model = unsafe { 20 | assert!(!models.is_null()); 21 | 22 | CStr::from_ptr(models) 23 | }; 24 | 25 | let model_str = model.to_str().unwrap(); 26 | let code_files: Vec = serde_json::from_str(model_str).unwrap(); 27 | 28 | let mut executor = RuleExecutor::new(code_files, guard_rules); 29 | executor.run(); 30 | 31 | let json = serde_json::to_string(&executor.errors).unwrap(); 32 | let c_str = CString::new(json).unwrap(); 33 | // println!("{:?}", c_str); 34 | c_str.into_raw() 35 | } -------------------------------------------------------------------------------- /guarding_core/src/domain/code_class.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::domain::code_function::CodeFunction; 4 | use crate::domain::CodePoint; 5 | 6 | #[repr(C)] 7 | #[derive(Serialize, Deserialize, Debug, Clone)] 8 | pub struct CodeClass { 9 | pub name: String, 10 | pub package: String, 11 | pub extends: Vec, 12 | pub implements: Vec, 13 | pub constant: Vec, 14 | pub functions: Vec, 15 | pub start: CodePoint, 16 | pub end: CodePoint 17 | } 18 | 19 | impl Default for CodeClass { 20 | fn default() -> Self { 21 | CodeClass { 22 | name: "".to_string(), 23 | package: "".to_string(), 24 | extends: vec![], 25 | implements: vec![], 26 | constant: vec![], 27 | functions: vec![], 28 | start: Default::default(), 29 | end: Default::default() 30 | } 31 | } 32 | } 33 | 34 | #[repr(C)] 35 | #[derive(Serialize, Deserialize, Debug, Clone)] 36 | pub struct ClassConstant { 37 | pub name: String, 38 | pub typ: String, 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Inherd Group 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | publish: 10 | name: Publish for ${{ matrix.os }} 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | include: 15 | - os: ubuntu-latest 16 | artifact_name: guarding 17 | asset_name: guarding-linux-amd64 18 | - os: windows-latest 19 | artifact_name: guarding.exe 20 | asset_name: guarding-windows-amd64 21 | - os: macos-latest 22 | artifact_name: guarding 23 | asset_name: guarding-macos-amd64 24 | 25 | steps: 26 | - uses: actions/checkout@v1 27 | 28 | - uses: actions-rs/toolchain@v1 29 | with: 30 | profile: minimal 31 | toolchain: stable 32 | 33 | - name: Build 34 | run: cargo build --release 35 | 36 | - name: Upload binaries to release 37 | uses: svenstaro/upload-release-action@v1-release 38 | with: 39 | repo_token: ${{ secrets.GITHUB_TOKEN }} 40 | file: target/release/${{ matrix.artifact_name }} 41 | asset_name: ${{ matrix.asset_name }} 42 | tag: ${{ github.ref }} 43 | -------------------------------------------------------------------------------- /guarding_ident/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "guarding_ident" 3 | version = "0.3.0" 4 | authors = ["Inherd Group "] 5 | edition = "2018" 6 | license = "MIT" 7 | readme = "README.md" 8 | repository = "https://github.com/inherd/guarding" 9 | documentation = "https://github.com/inherd/guarding" 10 | homepage = "https://github.com/inherd/guarding" 11 | description = """ 12 | Guarding is a guardians for code, architecture, layered. Guarding crate a architecture aguard DSL which based on ArchUnit. 13 | """ 14 | categories = ["text-processing", "command-line-interface", "development-tools"] 15 | exclude = [ 16 | ".github/*", 17 | ".gitattributes", 18 | ".adr.json", 19 | "guarding.guarding", 20 | "_fixtures", 21 | "docs", 22 | "examples", 23 | ] 24 | 25 | [dependencies] 26 | tree-sitter = "=0.19.3" 27 | tree-sitter-java = "=0.19.0" 28 | tree-sitter-javascript = "=0.19.0" 29 | tree-sitter-rust = "=0.19.0" 30 | tree-sitter-c-sharp = { git = "https://github.com/tree-sitter/tree-sitter-c-sharp" } 31 | 32 | # serialize 33 | serde = { version = "1.0", features = ["derive"] } 34 | serde_json = "1" 35 | 36 | walkdir = "2" 37 | 38 | guarding_core = { path = "../guarding_core", version = "0.2.7" } 39 | 40 | [lib] 41 | name = "guarding_ident" 42 | crate-type = ["lib"] 43 | -------------------------------------------------------------------------------- /guarding_core/src/rule_executor/rule_error.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[repr(C)] 4 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] 5 | pub enum MismatchType { 6 | None, 7 | Access, 8 | FileName, 9 | FileSize, 10 | } 11 | 12 | #[repr(C)] 13 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] 14 | pub struct RuleErrorMsg { 15 | pub expected: String, 16 | pub actual: String, 17 | pub mismatch_type: MismatchType, 18 | pub msg: String, 19 | pub items: Vec, 20 | pub rule_index: usize, 21 | } 22 | 23 | impl RuleErrorMsg { 24 | pub fn new(mismatch_type: MismatchType, index: usize) -> RuleErrorMsg { 25 | RuleErrorMsg { 26 | expected: "".to_string(), 27 | actual: "".to_string(), 28 | mismatch_type, 29 | msg: "".to_string(), 30 | items: vec![], 31 | rule_index: index 32 | } 33 | } 34 | } 35 | 36 | impl Default for RuleErrorMsg { 37 | fn default() -> Self { 38 | RuleErrorMsg { 39 | expected: "".to_string(), 40 | actual: "".to_string(), 41 | mismatch_type: MismatchType::None, 42 | msg: "".to_string(), 43 | items: vec![], 44 | rule_index: 0 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/refactor/switchcases/RegisterUsecase.java: -------------------------------------------------------------------------------- 1 | package com.phodal.pepper.refactor.switchcases; 2 | 3 | import com.phodal.pepper.refactor.switchcases.clz.CaseInterface; 4 | import org.reflections.Reflections; 5 | 6 | import java.util.HashMap; 7 | 8 | public class RegisterUsecase { 9 | HashMap caseMaps = new HashMap(); 10 | 11 | public void registerCase() { 12 | Reflections ref = new Reflections("com.phodal.pepper.refactor.switchcases"); 13 | for (Class cl : ref.getTypesAnnotatedWith(RegisterPattern.class)) { 14 | RegisterPattern findable = cl.getAnnotation(RegisterPattern.class); 15 | String className = cl.getName(); 16 | String registerName = findable.register(); 17 | caseMaps.put(registerName, className); 18 | } 19 | } 20 | 21 | public void runCase(String caseName) { 22 | String className = caseMaps.get(caseName); 23 | try { 24 | Class DemoClass = Class.forName(className); 25 | CaseInterface caseInterface = (CaseInterface) DemoClass.newInstance(); 26 | caseInterface.buildMap(); 27 | } catch (ClassNotFoundException e) { 28 | e.printStackTrace(); 29 | } catch (IllegalAccessException e) { 30 | e.printStackTrace(); 31 | } catch (InstantiationException e) { 32 | e.printStackTrace(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/refactor/staticclass/LogFileHandle.java: -------------------------------------------------------------------------------- 1 | package com.phodal.pepper.refactor.staticclass; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class LogFileHandle { 7 | public static void verifyFilesBySuffix(String suffix) { 8 | HashMap files = getFilesMap(); 9 | for (Map.Entry entry : files.entrySet()) { 10 | if (entry.getValue().endsWith(suffix)) { 11 | FileHandler.verify(entry.getValue()); 12 | } 13 | } 14 | } 15 | // 16 | // public static void verifyFilesBySuffix(String suffix) { 17 | // verifyFilesBySuffix(suffix, new FileVerifyFactoryImpl()); 18 | // } 19 | // 20 | // private static void verifyFilesBySuffix(String suffix, FileVerifyFactoryImpl fileVerifyFactoryImpl) { 21 | // HashMap files = getFilesMap(); 22 | // for (Map.Entry entry : files.entrySet()) { 23 | // if (entry.getValue().endsWith(suffix)) { 24 | // FileVerify fileVerify = fileVerifyFactoryImpl.genFileVerify(); 25 | // fileVerify.getVerify(entry); 26 | // } 27 | // } 28 | // } 29 | 30 | private static HashMap getFilesMap() { 31 | HashMap filesMap = new HashMap<>(); 32 | filesMap.put("home", "/home"); 33 | filesMap.put("about", "/pages/about"); 34 | return filesMap; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "guarding" 3 | version = "0.2.6" 4 | authors = ["Inherd Group "] 5 | edition = "2018" 6 | license = "MIT" 7 | readme = "README.md" 8 | repository = "https://github.com/inherd/guarding" 9 | documentation = "https://github.com/inherd/guarding" 10 | homepage = "https://github.com/inherd/guarding" 11 | description = """ 12 | Guarding is a guardians for code, architecture, layered. Guarding crate a architecture aguard DSL which based on ArchUnit. 13 | """ 14 | categories = ["text-processing", "command-line-interface", "development-tools"] 15 | exclude = [ 16 | ".github/*", 17 | ".gitattributes", 18 | ".adr.json", 19 | "guarding.guarding", 20 | "_fixtures", 21 | "docs", 22 | "examples", 23 | ] 24 | 25 | [lib] 26 | path = "src/lib.rs" 27 | 28 | [dependencies] 29 | # serialize 30 | serde = { version = "1.0", features = ["derive"] } 31 | serde_json = "1" 32 | 33 | regex = "1" 34 | 35 | clap = "3.0.0-beta.2" 36 | 37 | guarding_parser = { path = "guarding_parser", version = "0.2.6" } 38 | guarding_core = { path = "guarding_core", version = "0.2.6" } 39 | guarding_ident = { path = "guarding_ident", version = "0.3.0" } 40 | 41 | [dev-dependencies] 42 | criterion = { version = "0.3", features = ["html_reports"] } 43 | 44 | [[bench]] 45 | name = "my_benchmark" 46 | harness = false 47 | 48 | [build-dependencies] 49 | cc = "1.0" 50 | 51 | [workspace] 52 | members = [ 53 | "guarding_adapter", 54 | "guarding_parser", 55 | "guarding_core", 56 | "guarding_ident", 57 | ] -------------------------------------------------------------------------------- /guarding_parser/src/support/package_unify.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | pub struct PackageUnify {} 4 | 5 | impl PackageUnify { 6 | pub fn from_path(path: PathBuf) -> String { 7 | let mut package = "".to_string(); 8 | let path_iter = path.iter(); 9 | path_iter.for_each(|str| { 10 | package = format!("{}.{}", package, str.to_str().expect("error path")); 11 | }); 12 | 13 | package.remove(0); 14 | package 15 | } 16 | 17 | pub fn from_rust_import(str: &str, remove_last: bool) -> String { 18 | let mut package = "".to_string(); 19 | 20 | let mut vec = str.split("::").collect::>(); 21 | if remove_last { 22 | vec.remove(vec.len() - 1); 23 | } 24 | 25 | vec.iter().for_each(|sub| { 26 | package = format!("{}.{}", package, sub); 27 | }); 28 | 29 | package.remove(0); 30 | package 31 | } 32 | } 33 | 34 | #[cfg(test)] 35 | mod tests { 36 | use std::path::PathBuf; 37 | use crate::support::package_unify::PackageUnify; 38 | 39 | #[test] 40 | fn should_convert_path_to_package() { 41 | let buf = PathBuf::from("../../../src").join("core").join("domain"); 42 | assert_eq!(".........src.core.domain".to_string(), PackageUnify::from_path(buf)); 43 | } 44 | 45 | #[test] 46 | fn should_convert_rust_import() { 47 | let imp = "std::path::PathBuf"; 48 | assert_eq!("std.path".to_string(), PackageUnify::from_rust_import(imp, true)); 49 | 50 | let imp = "std::path"; 51 | assert_eq!("std.path".to_string(), PackageUnify::from_rust_import(imp, false)); 52 | } 53 | } -------------------------------------------------------------------------------- /examples/0.0.1.sample: -------------------------------------------------------------------------------- 1 | # 类::名 包含 "Controller"; 2 | # 中文分词:("..myapp..") 类名称中包含 "Controller" 3 | class("..myapp..")::function.name should contains(""); 4 | class("..myapp..")::function.name contains(""); 5 | 6 | class("..myapp..")::function.name should not contains(""); 7 | class("..myapp..")::function.name !contains(""); 8 | 9 | class("..myapp..")::vars.len should <= 20; 10 | class("..myapp..")::function.vars.len should <= 20; 11 | 12 | class(implementation "Connection.class")::name should endsWith "Connection"; 13 | class(extends "Connection.class")::name endsWith "Connection"; 14 | 15 | # todo: define in = inside = resideIn 16 | class(assignable "EntityManager.class") in package("..persistence."); 17 | class(assignable "EntityManager.class") inside package("..persistence."); 18 | 19 | class(assignable "EntityManager.class") resideIn package("..persistence."); 20 | 21 | class("com.myapp.(*)..") should freeOfCycles; 22 | 23 | # 模型之间的关系 24 | class("..myapp.model..")::relation.len should <= 5; 25 | 26 | class("..myapp.(**)") not dependBy ""; 27 | class("..service..") only accessed(["..controller..", "..service.."]); 28 | class("..service..")::name contains "Usecase" 29 | 30 | package("..home..")::file.len should < 20; 31 | # :: is Rust style, -> is C++ Style, so combine them 32 | package("..home..") -> file.len should < 20; 33 | 34 | # 正则表达式 35 | package(match("^/app")) endsWith "Connection"; 36 | 37 | package("..home..")::name should not contains(matching("")); 38 | 39 | # 简化的比较 40 | class::name.len should < 20; 41 | function::name.len should < 30; 42 | function::len should < 30; 43 | 44 | # 定制的分层架构 45 | # 多条规则 46 | layer("onion") 47 | ::domainModel("") 48 | ::domainService("") 49 | ::applicationService("") 50 | ::adapter("com.phodal.com", "zero"); 51 | 52 | -------------------------------------------------------------------------------- /guarding_parser/src/errors.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error as StdError; 2 | use std::fmt; 3 | 4 | #[derive(Debug)] 5 | pub enum ErrorKind { 6 | Msg(String), 7 | Json(serde_json::Error), 8 | Io(std::io::ErrorKind), 9 | } 10 | 11 | #[derive(Debug)] 12 | pub struct Error { 13 | /// Kind of error 14 | pub kind: ErrorKind, 15 | source: Option>, 16 | } 17 | 18 | 19 | impl fmt::Display for Error { 20 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 21 | match self.kind { 22 | ErrorKind::Msg(ref message) => write!(f, "{}", message), 23 | ErrorKind::Json(ref e) => write!(f, "{}", e), 24 | ErrorKind::Io(ref io_error) => { 25 | write!(f, "Io error while writing rendered value to output: {:?}", io_error) 26 | } 27 | } 28 | } 29 | } 30 | 31 | impl StdError for Error { 32 | fn source(&self) -> Option<&(dyn StdError + 'static)> { 33 | self.source.as_ref().map(|c| &**c as &(dyn StdError + 'static)) 34 | } 35 | } 36 | 37 | impl Error { 38 | /// Creates generic error 39 | pub fn msg(value: impl ToString) -> Self { 40 | Self { kind: ErrorKind::Msg(value.to_string()), source: None } 41 | } 42 | 43 | pub fn io_error(error: std::io::Error) -> Self { 44 | Self { kind: ErrorKind::Io(error.kind()), source: Some(Box::new(error)) } 45 | } 46 | 47 | pub fn json(value: serde_json::Error) -> Self { 48 | Self { kind: ErrorKind::Json(value), source: None } 49 | } 50 | 51 | } 52 | 53 | 54 | impl From for Error { 55 | fn from(error: std::io::Error) -> Self { 56 | Self::io_error(error) 57 | } 58 | } 59 | impl From<&str> for Error { 60 | fn from(e: &str) -> Self { 61 | Self::msg(e) 62 | } 63 | } 64 | impl From for Error { 65 | fn from(e: String) -> Self { 66 | Self::msg(e) 67 | } 68 | } 69 | impl From for Error { 70 | fn from(e: serde_json::Error) -> Self { 71 | Self::json(e) 72 | } 73 | } 74 | /// Convenient wrapper around std::Result. 75 | pub type Result = std::prelude::v1::Result; 76 | 77 | #[cfg(test)] 78 | mod tests { 79 | #[test] 80 | fn test_error_is_send_and_sync() { 81 | fn test_send_sync() {} 82 | 83 | test_send_sync::(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /_fixtures/java/src/main/java/com/phodal/pepper/powermock/SystemClassUser.java: -------------------------------------------------------------------------------- 1 | package com.phodal.pepper.powermock; 2 | 3 | import java.io.IOException; 4 | import java.io.UnsupportedEncodingException; 5 | import java.net.*; 6 | import java.util.Collections; 7 | import java.util.List; 8 | import java.util.UUID; 9 | 10 | public class SystemClassUser { 11 | public void threadSleep() throws InterruptedException { 12 | Thread.sleep(5000); 13 | } 14 | 15 | public String performEncode() throws UnsupportedEncodingException { 16 | return URLEncoder.encode("string", "utf8"); 17 | } 18 | 19 | public Process executeCommand() throws IOException { 20 | return Runtime.getRuntime().exec("command"); 21 | } 22 | 23 | public String getSystemProperty() throws IOException { 24 | return System.getProperty("property"); 25 | } 26 | 27 | public void doMoreComplicatedStuff() throws IOException { 28 | System.setProperty("nanoTime", Long.toString(System.nanoTime())); 29 | } 30 | 31 | public void copyProperty(String to, String from) throws IOException { 32 | System.setProperty(to, System.getProperty(from)); 33 | } 34 | 35 | public String format(String one, String args) throws IOException { 36 | return String.format(one, args); 37 | } 38 | 39 | public URL newURL(String anUrl) throws MalformedURLException { 40 | return new URL(anUrl); 41 | } 42 | 43 | public StringBuilder newStringBuilder() { 44 | return new StringBuilder(); 45 | } 46 | 47 | public void shuffleCollection(List list) { 48 | Collections.shuffle(list); 49 | } 50 | 51 | public URLConnection useURL(URL url) throws IOException { 52 | return url.openConnection(); 53 | } 54 | 55 | public InetAddress getLocalHost() throws IOException { 56 | return InetAddress.getLocalHost(); 57 | } 58 | 59 | public String generatePerishableToken() { 60 | final UUID uuid = UUID.randomUUID(); 61 | final String toString = uuid.toString(); 62 | final String result = toString.replaceAll("-", ""); 63 | return result; 64 | } 65 | 66 | public int lengthOf(StringBuilder to){ 67 | // The trick here is the casting to CharSequence, 68 | // this is to test that the runtime type(StringBuilder) is checked for mocked calls and not 69 | // the compile-time type (CharSequence) 70 | return lengthOf((CharSequence) to); 71 | } 72 | 73 | private int lengthOf(CharSequence to){ 74 | return to.length(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Guarding 2 | 3 | [![Build](https://github.com/inherd/guarding/actions/workflows/build.yml/badge.svg)](https://github.com/inherd/guarding/actions/workflows/build.yml) 4 | [![crates.io](https://img.shields.io/badge/crates.io-v0.2.1-orange.svg)](https://crates.io/crates/guarding) 5 | [![docs.rs](https://docs.rs/guarding/badge.svg)](https://docs.rs/guarding/) 6 | [![license](https://img.shields.io/crates/l/guarding)](https://github.com/inherd/guarding/blob/master/LICENSE) 7 | 8 | > Guarding is a guardians for code, architecture, layered. Using git hooks and DSL for design guard rules. 9 | 10 | Inspired by [ArchUnit](https://github.com/TNG/ArchUnit) 11 | 12 | ![Guarding Process](docs/guarding-process.svg) 13 | 14 | ## Usage 15 | 16 | ### use CLI 17 | 18 | 1. install 19 | 20 | ``` 21 | cargo install guarding 22 | ``` 23 | 24 | 2. create `guarding.guarding` file 25 | 26 | ``` 27 | package(".")::file.len should < 200; 28 | package(".")::file.len should > 50; 29 | ``` 30 | 31 | 3. run 32 | 33 | ``` 34 | guarding . 35 | ``` 36 | 37 | ### use API 38 | 39 | - `guarding_adapter`, FFI adapter, provide Guarding api, 40 | - `guarding_core`, core guarding model, 41 | - `guarding_ident`, identify different language: Java, JavaScript, Rust 42 | - `guarding_parser` parsing Guarding DSL 43 | 44 | ## Development 45 | 46 | workflow: 47 | 48 | 1. parsing guarding rules 49 | 2. parsing source code to models 50 | 3. capture rule with models 51 | 52 | DSL capture logic: 53 | 54 | 1. filter models from `rule_level` with `rule_scope` 55 | 2. run expression 56 | 3. run assert 57 | 58 | Queries Samples: [https://github.com/nvim-treesitter/nvim-treesitter/tree/master/queries](https://github.com/nvim-treesitter/nvim-treesitter/tree/master/queries) 59 | 60 | ### Guarding - Class or Struct function-name 61 | 62 | for packages: 63 | 64 | ``` 65 | class(implementation "BaseParser")::name should endsWith "Parser"; 66 | 67 | class("java.util.Map") only accessed(["com.phodal.pepper.refactor.staticclass"]); 68 | class(implementation "BaseParser")::name should not contains "Lexer"; 69 | ``` 70 | 71 | for Java, JavaScript 72 | 73 | ``` 74 | # 类::名 包含 "Controller"; 75 | # 中文分词:("..myapp..") 类名称中包含 "Controller" 76 | class("..myapp..")::function.name should contains("Model"); 77 | # or 78 | class("..myapp..")::function.name contains(""); 79 | ``` 80 | 81 | for Rust and Golang 82 | 83 | ``` 84 | struct("..myapp..")::function.name should contains("Model"); 85 | # or 86 | struct("..myapp..")::function.name contains(""); 87 | ``` 88 | 89 | License 90 | --- 91 | 92 | This code is distributed under the MIT license. See `LICENSE` in this directory. 93 | 94 | -------------------------------------------------------------------------------- /guarding_parser/src/support/str_support.rs: -------------------------------------------------------------------------------- 1 | // pest. The Elegant Parser 2 | // Copyright (c) 2018 Dragoș Tiselice 3 | // 4 | // Licensed under the Apache License, Version 2.0 5 | // or the MIT 6 | // license , at your 7 | // option. All files in the project carrying such notice may not be copied, 8 | // modified, or distributed except according to those terms. 9 | 10 | use std::char; 11 | 12 | /// Strings are delimited by double quotes, single quotes and backticks 13 | /// We need to remove those before putting them in the AST 14 | pub fn replace_string_markers(input: &str) -> String { 15 | match input.chars().next().unwrap() { 16 | '"' => input.replace('"', ""), 17 | '\'' => input.replace('\'', ""), 18 | '`' => input.replace('`', ""), 19 | _ => unreachable!("How did you even get there: {:?}", input), 20 | } 21 | } 22 | 23 | pub fn unescape(string: &str) -> Option { 24 | let mut result = String::new(); 25 | let mut chars = string.chars(); 26 | 27 | loop { 28 | match chars.next() { 29 | Some('\\') => match chars.next()? { 30 | '"' => result.push('"'), 31 | '\\' => result.push('\\'), 32 | 'r' => result.push('\r'), 33 | 'n' => result.push('\n'), 34 | 't' => result.push('\t'), 35 | '0' => result.push('\0'), 36 | '\'' => result.push('\''), 37 | 'x' => { 38 | let string: String = chars.clone().take(2).collect(); 39 | 40 | if string.len() != 2 { 41 | return None; 42 | } 43 | 44 | for _ in 0..string.len() { 45 | chars.next()?; 46 | } 47 | 48 | let value = u8::from_str_radix(&string, 16).ok()?; 49 | 50 | result.push(char::from(value)); 51 | } 52 | 'u' => { 53 | if chars.next()? != '{' { 54 | return None; 55 | } 56 | 57 | let string: String = chars.clone().take_while(|c| *c != '}').collect(); 58 | 59 | if string.len() < 2 || 6 < string.len() { 60 | return None; 61 | } 62 | 63 | for _ in 0..string.len() + 1 { 64 | chars.next()?; 65 | } 66 | 67 | let value = u32::from_str_radix(&string, 16).ok()?; 68 | 69 | result.push(char::from_u32(value)?); 70 | } 71 | _ => return None, 72 | }, 73 | Some(c) => result.push(c), 74 | None => return Some(result), 75 | }; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /guarding_ident/src/model_builder.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::path::{Path, PathBuf}; 3 | 4 | use walkdir::WalkDir; 5 | 6 | use guarding_core::domain::code_file::CodeFile; 7 | use crate::identify::c_sharp_ident::CSharpIdent; 8 | use crate::identify::code_ident::CodeIdent; 9 | use crate::identify::java_ident::JavaIdent; 10 | use crate::identify::js_ident::JsIdent; 11 | use crate::identify::rust_ident::RustIdent; 12 | 13 | pub struct ModelBuilder {} 14 | 15 | impl ModelBuilder { 16 | pub fn build_models_by_dir(code_dir: PathBuf) -> Vec { 17 | let mut models = vec![]; 18 | for entry in WalkDir::new(code_dir) { 19 | let entry = entry.unwrap(); 20 | if !entry.file_type().is_file() { 21 | continue; 22 | } 23 | 24 | let path = entry.path(); 25 | if let None = path.extension() { 26 | continue; 27 | } 28 | 29 | ModelBuilder::build_model_by_file(&mut models, path) 30 | } 31 | models 32 | } 33 | 34 | pub fn build_model_by_file(models: &mut Vec, path: &Path) { 35 | let ext = path.extension().unwrap().to_str().unwrap(); 36 | let file_name = path.file_name().unwrap().to_str().unwrap(); 37 | 38 | match ext { 39 | "java" => { 40 | let mut file = JavaIdent::parse(ModelBuilder::read_content(path).as_str()); 41 | file.path = ModelBuilder::format_path(path); 42 | file.file_name = file_name.to_string(); 43 | models.push(file); 44 | } 45 | "js" => { 46 | let mut file = JsIdent::parse(ModelBuilder::read_content(path).as_str()); 47 | file.path = format!("{}", path.display()); 48 | file.file_name = file_name.to_string(); 49 | models.push(file); 50 | } 51 | "rs" => { 52 | let mut file = RustIdent::parse(ModelBuilder::read_content(path).as_str()); 53 | file.path = format!("{}", path.display()); 54 | file.file_name = file_name.to_string(); 55 | models.push(file); 56 | } 57 | "cs" => { 58 | let mut file = CSharpIdent::parse(ModelBuilder::read_content(path).as_str()); 59 | file.path = format!("{}", path.display()); 60 | file.file_name = file_name.to_string(); 61 | models.push(file); 62 | } 63 | &_ => {} 64 | } 65 | } 66 | 67 | fn read_content(path: &Path) -> String { 68 | fs::read_to_string(path).expect("not such file") 69 | } 70 | 71 | fn format_path(path: &Path) -> String { 72 | format!("{}", path.display()) 73 | } 74 | } 75 | 76 | 77 | #[cfg(test)] 78 | mod tests { 79 | use std::env; 80 | use crate::ModelBuilder; 81 | 82 | #[test] 83 | fn should_parse_current_dir() { 84 | let dir = env::current_dir().unwrap(); 85 | let models = ModelBuilder::build_models_by_dir(dir); 86 | 87 | assert!(models.len() > 0); 88 | } 89 | } -------------------------------------------------------------------------------- /guarding_parser/src/guarding.pest: -------------------------------------------------------------------------------- 1 | // online parser: [https://pest.rs/](https://pest.rs/) 2 | start = _{ SOI ~ declaration* ~ EOI} 3 | 4 | identifier = @{ (ASCII_ALPHA | ASCII_ALPHANUMERIC | "_") ~ (ASCII_ALPHANUMERIC | "_")* } 5 | 6 | declaration = { 7 | normal_rule | 8 | layer_rule 9 | } 10 | 11 | // package is a container of file and classes 12 | // file is a container of classes and functions 13 | // classes is a container of functions and field 14 | normal_rule = { 15 | rule_level ~ ("(" ~ scope ~ ")")? ~ (use_symbol ~ expression)? ~ should? ~ only? ~ operator ~ assert ~ ";"? 16 | } 17 | 18 | rule_level = { 19 | "package" | 20 | "class" | 21 | "struct" | 22 | "function" | 23 | "file" 24 | } 25 | 26 | layer_rule = { 27 | "layer" ~ "(" ~ layer_type ~ ")" ~ (use_symbol ~ layer_expression)* ~ ";"? 28 | } 29 | 30 | // use property 31 | use_symbol = { 32 | "::" | 33 | "->" 34 | } 35 | 36 | layer_type = { 37 | string 38 | } 39 | 40 | layer_expression = { 41 | identifier ~ "(" ~ string ~ (comma ~ string)* ~ ")" 42 | } 43 | 44 | scope = { 45 | path_scope | 46 | impl_scope | 47 | extend_scope | 48 | assignable_scope | 49 | match_scope 50 | } 51 | 52 | path_scope = { 53 | string 54 | } 55 | 56 | match_scope = { 57 | "match" ~ "(" ~ string ~ ")" 58 | } 59 | assignable_scope = { 60 | "assignable" ~ string 61 | } 62 | extend_scope = { 63 | "extends" ~ string 64 | } 65 | 66 | impl_scope = { 67 | "implementation" ~ string 68 | } 69 | 70 | expression = { 71 | fn_call 72 | } 73 | 74 | fn_call = { 75 | identifier ~ (dot ~ identifier )* 76 | } 77 | 78 | assert = { 79 | leveled | 80 | stringed | 81 | array_stringed | 82 | sized 83 | } 84 | 85 | array_stringed = { 86 | "(" ~ "[" ~ string ~ ("," ~ string)* ~ "]" ~ ")" 87 | } 88 | 89 | stringed = { 90 | "("? ~ string ~ ")"? 91 | } 92 | 93 | leveled = { 94 | rule_level ~ "(" ~ string ~ ")" 95 | } 96 | 97 | sized = { 98 | int 99 | } 100 | 101 | operator = { 102 | op_not ~ operator | 103 | op_not_symbol ~ operator | 104 | op_lte | 105 | op_gte | 106 | op_lt | 107 | op_gt | 108 | op_eq | 109 | op_ineq | 110 | op_contains | 111 | op_endsWith | 112 | op_startsWith | 113 | op_resideIn | 114 | op_inside | 115 | op_accessed | 116 | op_dependBy 117 | } 118 | 119 | // todo: change to strings operations method 120 | op_contains = { "contains" } 121 | op_endsWith = { "endsWith" } 122 | op_startsWith = { "startsWith" } 123 | 124 | // todo: thinking in define packages ops 125 | op_inside = { "inside" } 126 | op_resideIn = { "resideIn" } 127 | op_accessed = { "accessed" } 128 | op_dependBy = { "dependBy" } 129 | 130 | op_not = @{ "not" } 131 | op_not_symbol = @{ "!" } 132 | 133 | // todo: move to comparison; 134 | op_lte = { "<=" } 135 | op_gte = { ">=" } 136 | op_lt = { "<" } 137 | op_gt = { ">" } 138 | op_eq = { "=" } 139 | op_ineq = { "!=" } 140 | 141 | should = { "should" } 142 | only = { "only" } 143 | 144 | double_quoted_string = @{ "\"" ~ (!("\"") ~ ANY)* ~ "\""} 145 | single_quoted_string = @{ "\'" ~ (!("\'") ~ ANY)* ~ "\'"} 146 | 147 | string = @{ 148 | double_quoted_string | 149 | single_quoted_string 150 | } 151 | 152 | number = @{ '0'..'9'+ } 153 | int = @{ number | "-" ~ "0"* ~ '1'..'9' ~ number? } 154 | 155 | dot = { "." } 156 | comma = { "," } 157 | semicolon = { ";" } 158 | opening_paren = { "(" } 159 | closing_paren = { ")" } 160 | 161 | 162 | newline = _{ "\n" | "\r\n" } 163 | WHITESPACE = _{ " " | "\t" | newline } 164 | block_comment = _{ "/*" ~ (block_comment | !"*/" ~ ANY)* ~ "*/" } 165 | COMMENT = _{ block_comment | ("//" ~ (!newline ~ ANY)*) } -------------------------------------------------------------------------------- /guarding_core/src/rule_executor/package_matcher.rs: -------------------------------------------------------------------------------- 1 | /* Rewrite from Java version 2 | * Copyright 2014-2021 TNG Technology Consulting GmbH 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 | use regex::Regex; 17 | 18 | pub fn is_package_match(package_identifier: String, text: &str) -> bool { 19 | let package = convert_to_regex(package_identifier); 20 | let regex = Regex::new(package.as_str()) 21 | .expect("regex error"); 22 | 23 | regex.is_match(text) 24 | } 25 | 26 | pub fn is_assert_match(package_identifier: String, text: &str, assert_package: String) -> bool { 27 | let package = convert_to_regex(package_identifier); 28 | let regex = Regex::new(package.as_str()) 29 | .expect("regex error"); 30 | 31 | if let Some(caps) = regex.captures(text) { 32 | for ma in caps.iter() { 33 | if let Some(match_) = ma { 34 | if match_.as_str() == assert_package { 35 | return true 36 | } 37 | } 38 | } 39 | } 40 | 41 | return false; 42 | } 43 | 44 | pub fn convert_to_regex(package_identifier: String) -> String { 45 | let replaced = package_identifier 46 | .replace("(**)", "#%#%#") 47 | .replace("*", "\\w+") 48 | .replace(".", "\\.") 49 | .replace("#%#%#", "(\\w+(?:\\.\\w+)*)") 50 | .replace("\\.\\.", "(?:(?:^\\w*)?\\.(?:\\w+\\.)*(?:\\w*$)?)?"); 51 | 52 | format!("^{}$", replaced) 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use crate::rule_executor::package_matcher::{is_package_match, is_assert_match}; 58 | 59 | #[test] 60 | fn should_match() { 61 | let values = vec![ 62 | "some.arbitrary.pkg | some.arbitrary.pkg | true", 63 | "some.arbitrary.pkg | some.thing.different | false", 64 | "some..pkg | some.arbitrary.pkg | true", 65 | "some..middle..pkg | some.arbitrary.middle.more.pkg | true", 66 | "*..pkg | some.arbitrary.pkg | true", 67 | "some..* | some.arbitrary.pkg | true", 68 | "*..pkg | some.arbitrary.pkg.toomuch | false", 69 | "toomuch.some..* | some.arbitrary.pkg | false", 70 | "*..wrong | some.arbitrary.pkg | false", 71 | "some..* | wrong.arbitrary.pkg | false", 72 | "..some | some | true", 73 | "some.. | some | true", 74 | "*..some | some | false", 75 | "some..* | some | false", 76 | "..some | asome | false", 77 | "some.. | somea | false", 78 | "*.*.* | wrong.arbitrary.pkg | true", 79 | "*.*.* | wrong.arbitrary.pkg.toomuch | false", 80 | "some.arbi*.pk*.. | some.arbitrary.pkg.whatever | true", 81 | "some.arbi*.. | some.brbitrary.pkg | false", 82 | "some.*rary.*kg.. | some.arbitrary.pkg.whatever | true", 83 | "some.*rary.. | some.arbitrarz.pkg | false", 84 | "some.pkg | someepkg | false", 85 | "..pkg.. | some.random.pkg.maybe.anywhere | true", 86 | "..p.. | s.r.p.m.a | true", 87 | "*..pkg..* | some.random.pkg.maybe.anywhere | true", 88 | "*..p..* | s.r.p.m.a | true"]; 89 | 90 | for value in values { 91 | let split = value.split(" | "); 92 | let vec = split.collect::>(); 93 | 94 | let assert: bool = vec[2] 95 | .parse() 96 | .expect("convert bool error"); 97 | 98 | assert_eq!(assert, is_package_match(vec[0].to_string(), vec[1])); 99 | } 100 | } 101 | 102 | #[test] 103 | fn should_match_group() { 104 | let assert_match = is_assert_match("some.(*).pkg".to_string(), 105 | "some.arbitrary.pkg", 106 | "arbitrary".to_string() 107 | ); 108 | 109 | assert_eq!(true, assert_match); 110 | } 111 | } -------------------------------------------------------------------------------- /guarding_parser/src/ast.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | #[derive(Clone, Debug, Eq, PartialEq)] 4 | pub struct GuardRule { 5 | pub origin: String, 6 | pub ty: RuleType, 7 | pub level: RuleLevel, 8 | pub scope: RuleScope, 9 | pub expr: Expr, 10 | pub ops: Vec, 11 | pub assert: RuleAssert 12 | } 13 | 14 | #[derive(Clone, Debug, Eq, PartialEq)] 15 | pub enum LayeredRule { 16 | Normal(NormalLayered), 17 | Onion(OnionArch) 18 | } 19 | 20 | #[derive(Clone, Debug, Eq, PartialEq)] 21 | pub struct NormalLayered { 22 | 23 | } 24 | 25 | #[derive(Clone, Debug, Eq, PartialEq)] 26 | pub struct OnionArch { 27 | 28 | } 29 | 30 | 31 | impl Default for GuardRule { 32 | fn default() -> Self { 33 | GuardRule { 34 | origin: "".to_string(), 35 | ty: RuleType::Normal, 36 | level: RuleLevel::Class, 37 | scope: RuleScope::All, 38 | expr: Expr::Identifier("".to_string()), 39 | ops: vec![], 40 | assert: RuleAssert::Empty 41 | } 42 | } 43 | } 44 | 45 | impl GuardRule { 46 | pub fn assert_sized(rule: &GuardRule) -> usize { 47 | let mut size = 0; 48 | match &rule.assert { 49 | RuleAssert::Sized(sized) => { 50 | size = *sized; 51 | } 52 | _ => {} 53 | } 54 | size 55 | } 56 | 57 | pub fn assert_string(rule: &GuardRule) -> String { 58 | let mut string = "".to_string(); 59 | match &rule.assert { 60 | RuleAssert::Stringed(str) => { 61 | string = str.clone(); 62 | } 63 | _ => {} 64 | } 65 | string 66 | } 67 | 68 | pub fn package_level(rule: &GuardRule) -> (bool, RuleLevel, String) { 69 | let mut string = "".to_string(); 70 | let mut level = RuleLevel::Package; 71 | let mut has_capture = false; 72 | match &rule.assert { 73 | RuleAssert::Leveled(lv, package_ident) => { 74 | has_capture = true; 75 | level = lv.clone(); 76 | string = package_ident.clone(); 77 | } 78 | _ => {} 79 | } 80 | 81 | return (has_capture, level, string); 82 | } 83 | } 84 | 85 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 86 | pub enum RuleType { 87 | Normal, 88 | Layer, 89 | } 90 | 91 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 92 | pub enum RuleLevel { 93 | Package, 94 | Function, 95 | Class, 96 | Struct, 97 | } 98 | 99 | #[derive(Clone, Debug, Eq, PartialEq)] 100 | pub enum RuleScope { 101 | All, 102 | PathDefine(String), 103 | Extend(String), 104 | Assignable(String), 105 | Implementation(String), 106 | MatchRegex(String), 107 | } 108 | 109 | #[derive(Clone, Debug, Eq, PartialEq)] 110 | pub enum Expr { 111 | PropsCall(Vec), 112 | Identifier(String) 113 | } 114 | 115 | /// A function call, can be a filter or a global function 116 | #[derive(Clone, Debug, Eq, PartialEq)] 117 | pub struct FunctionCall { 118 | /// The name of the function 119 | pub name: String, 120 | /// The args of the function: key -> value 121 | pub args: HashMap, 122 | } 123 | 124 | impl FunctionCall { 125 | pub fn new(name: String) -> FunctionCall { 126 | FunctionCall { 127 | name, 128 | args: Default::default() 129 | } 130 | } 131 | } 132 | 133 | impl Default for FunctionCall { 134 | fn default() -> Self { 135 | FunctionCall { 136 | name: "".to_string(), 137 | args: Default::default() 138 | } 139 | } 140 | } 141 | 142 | #[derive(Clone, Debug, Eq, PartialEq)] 143 | pub enum Operator { 144 | /// > 145 | Gt, 146 | /// >= 147 | Gte, 148 | /// < 149 | Lt, 150 | /// <= 151 | Lte, 152 | /// == 153 | Eq, 154 | /// != 155 | Ineq, 156 | /// and 157 | And, 158 | /// or 159 | Or, 160 | /// ! 161 | /// not 162 | Not, 163 | 164 | // string assert operator 165 | StartsWith, 166 | Endswith, 167 | Contains, 168 | 169 | // package operators 170 | Inside, 171 | ResideIn, 172 | Accessed, 173 | DependBy 174 | } 175 | 176 | #[derive(Clone, Debug, Eq, PartialEq)] 177 | pub enum RuleAssert { 178 | Empty, 179 | Stringed(String), 180 | Leveled(RuleLevel, String), 181 | ArrayStringed(Vec), 182 | Sized(usize), 183 | } -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::path::PathBuf; 3 | 4 | use guarding_core::rule_executor::rule_error::MismatchType; 5 | use crate::exec_guarding; 6 | 7 | fn test_dir() -> PathBuf { 8 | PathBuf::from(env!("CARGO_MANIFEST_DIR")) 9 | .join("_fixtures") 10 | .join("java") 11 | } 12 | 13 | #[test] 14 | fn should_working_in_process() { 15 | let path = test_dir().join("size.guarding"); 16 | let content = fs::read_to_string(path).expect("not file"); 17 | let code_dir = test_dir(); 18 | 19 | let errors = exec_guarding(content, code_dir); 20 | 21 | assert_eq!(1, errors.len()); 22 | assert_eq!("50".to_string(), errors[0].expected); 23 | assert_eq!("26".to_string(), errors[0].actual); 24 | assert_eq!(MismatchType::FileSize, errors[0].mismatch_type); 25 | assert_eq!("file.len = 26, expected: len > 50".to_string(), errors[0].msg); 26 | } 27 | 28 | #[test] 29 | fn should_get_errors_when_lt() { 30 | let content = "package(\".\")::file.len should = 27;"; 31 | let errors = exec_guarding(content.to_string(), test_dir()); 32 | 33 | assert_eq!(1, errors.len()); 34 | } 35 | 36 | #[test] 37 | fn should_support_filter() { 38 | let content = "package(\"com.phodal.pepper.refactor.parser\")::file.len should = 3;"; 39 | let errors = exec_guarding(content.to_string(), test_dir()); 40 | 41 | assert_eq!(0, errors.len()); 42 | } 43 | 44 | #[test] 45 | fn should_support_for_class_filter() { 46 | let content = "class(\".\")::len should < 25; 47 | class(\".\")::len should > 20;"; 48 | let errors = exec_guarding(content.to_string(), test_dir()); 49 | 50 | assert_eq!(0, errors.len()); 51 | } 52 | 53 | #[test] 54 | fn should_support_for_extends_count() { 55 | let content = "class(implementation \"BaseParser\")::len = 2"; 56 | let errors = exec_guarding(content.to_string(), test_dir()); 57 | 58 | assert_eq!(0, errors.len()); 59 | } 60 | 61 | #[test] 62 | fn should_support_for_extends_ends_with() { 63 | let content = "class(implementation \"BaseParser\")::name should endsWith \"Parser2\";"; 64 | let errors = exec_guarding(content.to_string(), test_dir()); 65 | assert_eq!(1, errors.len()); 66 | assert_eq!(2, errors[0].items.len()); 67 | 68 | let correct_content = "class(implementation \"BaseParser\")::name should endsWith \"Parser\";"; 69 | let errors = exec_guarding(correct_content.to_string(), test_dir()); 70 | assert_eq!(0, errors.len()); 71 | } 72 | 73 | #[test] 74 | fn should_support_for_starts_with() { 75 | let content = "class(implementation \"BaseParser\")::name should startsWith \"Json\";"; 76 | let errors = exec_guarding(content.to_string(), test_dir()); 77 | assert_eq!(1, errors.len()); 78 | assert_eq!(1, errors[0].items.len()); 79 | } 80 | 81 | #[test] 82 | fn should_support_for_reside_in() { 83 | let content = "class(implementation \"BaseParser\") resideIn package(\"....parser2\");"; 84 | let errors = exec_guarding(content.to_string(), test_dir()); 85 | 86 | assert_eq!(1, errors.len()); 87 | 88 | let content = "class(implementation \"BaseParser\") resideIn package(\"....parser\");"; 89 | let errors = exec_guarding(content.to_string(), test_dir()); 90 | 91 | assert_eq!(0, errors.len()); 92 | } 93 | 94 | #[test] 95 | fn should_support_for_not_reside_in() { 96 | let content = "class(implementation \"BaseParser\") not resideIn package(\"....parser2\");"; 97 | let errors = exec_guarding(content.to_string(), test_dir()); 98 | 99 | assert_eq!(0, errors.len()); 100 | 101 | let content = "class(implementation \"BaseParser\") not resideIn package(\"....parser\");"; 102 | let errors = exec_guarding(content.to_string(), test_dir()); 103 | 104 | assert_eq!(1, errors.len()); 105 | } 106 | 107 | #[test] 108 | fn should_support_for_contains() { 109 | let content = "class(implementation \"BaseParser\")::name should contains \"Lexer\";"; 110 | let errors = exec_guarding(content.to_string(), test_dir()); 111 | assert_eq!(1, errors.len()); 112 | assert_eq!(2, errors[0].items.len()); 113 | 114 | let content = "class(implementation \"BaseParser\")::name should contains \"Parser\";"; 115 | let errors = exec_guarding(content.to_string(), test_dir()); 116 | assert_eq!(0, errors.len()); 117 | } 118 | 119 | #[test] 120 | fn should_support_for_not_contains() { 121 | let content = "class(implementation \"BaseParser\")::name should not contains \"Lexer\";"; 122 | let errors = exec_guarding(content.to_string(), test_dir()); 123 | assert_eq!(0, errors.len()); 124 | 125 | let content = "class(implementation \"BaseParser\")::name should not contains \"Parser\";"; 126 | let errors = exec_guarding(content.to_string(), test_dir()); 127 | assert_eq!(1, errors.len()); 128 | } 129 | 130 | #[test] 131 | fn should_support_for_accessed() { 132 | let content = "class(\"java.util.Map\") only accessed([\"com.phodal.pepper.refactor.staticclass\"]);"; 133 | let errors = exec_guarding(content.to_string(), test_dir()); 134 | 135 | assert_eq!(1, errors.len()); 136 | assert!(errors[0].items[0].contains("MyDictionary.java")) 137 | } 138 | -------------------------------------------------------------------------------- /guarding_ident/src/identify/js_ident.rs: -------------------------------------------------------------------------------- 1 | use tree_sitter::{Node, Parser, Query, QueryCursor}; 2 | 3 | use guarding_core::domain::code_file::CodeFile; 4 | use guarding_core::domain::code_class::CodeClass; 5 | use crate::code_ident::CodeIdent; 6 | 7 | const JS_QUERY: &'static str = " 8 | (import_specifier 9 | name: (identifier) @import-name) 10 | (namespace_import (identifier) @import-name) 11 | (import_statement 12 | source: (string) @source) 13 | (import_clause (identifier) @import-name) 14 | 15 | (class_declaration 16 | name: (identifier) @class-name 17 | body: (class_body 18 | (method_definition 19 | name: (property_identifier) @class-method-name 20 | parameters: (formal_parameters (identifier)? @parameter) 21 | ) 22 | ) 23 | ) 24 | 25 | (program (function_declaration 26 | name: * @function-name)) 27 | "; 28 | 29 | pub struct JsIdent { 30 | parser: Parser, 31 | query: Query, 32 | } 33 | 34 | 35 | impl JsIdent { 36 | fn new() -> JsIdent { 37 | let mut parser = Parser::new(); 38 | 39 | let language = tree_sitter_javascript::language(); 40 | parser.set_language(language).unwrap(); 41 | 42 | let query = Query::new(language, &JS_QUERY) 43 | .map_err(|e| println!("{}", format!("Query compilation failed: {:?}", e))).unwrap(); 44 | JsIdent { parser, query } 45 | } 46 | } 47 | 48 | impl JsIdent { 49 | fn do_parse(code: &str, ident: &mut JsIdent) -> CodeFile { 50 | let text_callback = |n: Node| &code[n.byte_range()]; 51 | let tree = ident.parser.parse(code, None).unwrap(); 52 | 53 | let mut query_cursor = QueryCursor::new(); 54 | let captures = query_cursor.captures(&ident.query, tree.root_node(), text_callback); 55 | 56 | let mut code_file = CodeFile::default(); 57 | let mut last_class_end_line = 0; 58 | let mut class = CodeClass::default(); 59 | 60 | for (mat, capture_index) in captures { 61 | let capture = mat.captures[capture_index]; 62 | let capture_name = &ident.query.capture_names()[capture.index as usize]; 63 | 64 | let text = capture.node.utf8_text((&code).as_ref()).unwrap_or(""); 65 | match capture_name.as_str() { 66 | "source" => { 67 | code_file.imports.push(text.to_string()); 68 | } 69 | "class-name" => { 70 | class.name = text.to_string(); 71 | let class_node = capture.node.parent().unwrap(); 72 | last_class_end_line = class_node.end_position().row; 73 | JsIdent::insert_location(&mut class, class_node); 74 | } 75 | "class-method-name" => { 76 | class.functions.push(JsIdent::create_function(capture, text)); 77 | } 78 | "function-name" => { 79 | code_file.functions.push(JsIdent::create_function(capture, text)); 80 | } 81 | "import-name" => {} 82 | "parameter" => {} 83 | &_ => { 84 | println!( 85 | " pattern: {}, capture: {}, row: {}, text: {:?}", 86 | mat.pattern_index, 87 | capture_name, 88 | capture.node.start_position().row, 89 | capture.node.utf8_text((&code).as_ref()).unwrap_or("") 90 | ); 91 | } 92 | } 93 | 94 | if capture.node.start_position().row >= last_class_end_line { 95 | if !class.name.is_empty() { 96 | code_file.classes.push(class.clone()); 97 | class = CodeClass::default(); 98 | } 99 | } 100 | } 101 | 102 | code_file 103 | } 104 | } 105 | 106 | impl CodeIdent for JsIdent { 107 | fn parse(code: &str) -> CodeFile { 108 | let mut ident = JsIdent::new(); 109 | JsIdent::do_parse(code, &mut ident) 110 | } 111 | } 112 | 113 | #[cfg(test)] 114 | mod tests { 115 | use crate::code_ident::CodeIdent; 116 | use crate::js_ident::JsIdent; 117 | 118 | #[test] 119 | fn should_parse_import() { 120 | let source_code = "import {sayHi} from './say.js' 121 | 122 | class Rectangle { 123 | constructor(height, width) { 124 | this.height = height; 125 | this.width = width; 126 | } 127 | } 128 | 129 | function abc() { 130 | 131 | } 132 | "; 133 | let file = JsIdent::parse(source_code); 134 | let funcs = &file.functions[0]; 135 | let class = &file.classes[0]; 136 | 137 | assert_eq!("Rectangle", class.name); 138 | assert_eq!(0, class.start.column); 139 | assert_eq!(2, class.start.row); 140 | assert_eq!(7, class.end.row); 141 | assert_eq!(1, class.end.column); 142 | assert_eq!("constructor", class.functions[0].name); 143 | assert_eq!("abc", funcs.name); 144 | } 145 | 146 | #[test] 147 | fn should_parse_func_location() { 148 | let source_code = "function abc() { 149 | 150 | } 151 | "; 152 | let file = JsIdent::parse(source_code); 153 | 154 | let funcs = &file.functions[0]; 155 | assert_eq!("abc", funcs.name); 156 | 157 | assert_eq!(0, funcs.start.row); 158 | assert_eq!(0, funcs.start.column); 159 | assert_eq!(2, funcs.end.row); 160 | assert_eq!(1, funcs.end.column); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /guarding_ident/src/identify/c_sharp_ident.rs: -------------------------------------------------------------------------------- 1 | use tree_sitter::{Node, Parser, Query, QueryCursor}; 2 | use guarding_core::domain::code_class::CodeClass; 3 | use guarding_core::domain::code_file::CodeFile; 4 | use guarding_core::domain::code_function::CodeFunction; 5 | use crate::code_ident::CodeIdent; 6 | 7 | const C_SHARP_QUERY: &'static str = " 8 | (using_directive 9 | (qualified_name) @import-name) 10 | 11 | (class_declaration 12 | (attribute_list (attribute name: (identifier) @annotation.name))? 13 | name: (identifier) @class-name 14 | bases: (base_list ((identifier) @impl-name))? 15 | body: (declaration_list 16 | (property_declaration 17 | type: (identifier) @prop-type 18 | name: (identifier) @prop-name 19 | )? 20 | (method_declaration 21 | name: (identifier) @method-name 22 | parameters: (parameter_list (parameter 23 | type: (identifier) @param-type 24 | name: (identifier) @param-name 25 | ))? 26 | )? 27 | ) @body 28 | )"; 29 | 30 | 31 | pub struct CSharpIdent { 32 | parser: Parser, 33 | query: Query 34 | } 35 | 36 | impl CSharpIdent { 37 | pub fn new() -> CSharpIdent { 38 | let mut parser = Parser::new(); 39 | let language = tree_sitter_c_sharp::language(); 40 | parser.set_language(language).unwrap(); 41 | 42 | let query = Query::new(language, &C_SHARP_QUERY) 43 | .map_err(|e| println!("{}", format!("Query compilation failed: {:?}", e))).unwrap(); 44 | 45 | CSharpIdent { 46 | parser, 47 | query 48 | } 49 | } 50 | 51 | fn do_parse(code: &&str, ident: &mut CSharpIdent) -> CodeFile { 52 | let tree = ident.parser.parse(code, None).unwrap(); 53 | let text_callback = |n: Node| &code[n.byte_range()]; 54 | let mut query_cursor = QueryCursor::new(); 55 | let captures = query_cursor.captures(&ident.query, tree.root_node(), text_callback); 56 | 57 | let mut code_file = CodeFile::default(); 58 | let mut class = CodeClass::default(); 59 | 60 | let capture_names = ident.query.capture_names(); 61 | 62 | let mut iters = captures.into_iter(); 63 | while let Some((mat, capture_index)) = iters.next() { 64 | let capture = mat.captures[capture_index]; 65 | let capture_name: &str = &capture_names[capture.index as usize]; 66 | 67 | let text = capture.node.utf8_text((&code).as_ref()).unwrap_or(""); 68 | match capture_name { 69 | "import-name" => { 70 | code_file.imports.push(text.to_string()); 71 | } 72 | "class-name" => { 73 | class.name = text.to_string(); 74 | class.package = code_file.package.clone(); 75 | 76 | let class_node = capture.node.parent().unwrap(); 77 | CSharpIdent::insert_location(&mut class, class_node); 78 | } 79 | "impl-name" => { 80 | class.implements.push(text.to_string()); 81 | } 82 | "method-name" => { 83 | let mut function = CodeFunction::default(); 84 | function.name = text.to_string(); 85 | class.functions.push(function); 86 | 87 | if !class.name.is_empty() { 88 | code_file.classes.push(class.clone()); 89 | class = CodeClass::default(); 90 | } 91 | } 92 | &_ => { 93 | println!( 94 | " pattern: {}, capture: {}, row: {}, text: {:?}", 95 | mat.pattern_index, 96 | capture_name, 97 | capture.node.start_position().row, 98 | capture.node.utf8_text((&code).as_ref()).unwrap_or("") 99 | ); 100 | } 101 | } 102 | } 103 | 104 | if class.name != "" { 105 | code_file.classes.push(class.clone()); 106 | } 107 | 108 | code_file 109 | } 110 | } 111 | 112 | impl CodeIdent for CSharpIdent { 113 | fn parse(code: &str) -> CodeFile { 114 | let mut ident = CSharpIdent::new(); 115 | CSharpIdent::do_parse(&code, &mut ident) 116 | } 117 | } 118 | 119 | #[cfg(test)] 120 | mod tests { 121 | use crate::code_ident::CodeIdent; 122 | use crate::identify::c_sharp_ident::CSharpIdent; 123 | 124 | #[test] 125 | fn should_parse_import() { 126 | let source_code = "using Microsoft.CodeAnalysis;"; 127 | 128 | let file = CSharpIdent::parse(source_code); 129 | assert_eq!(1, file.imports.len()); 130 | } 131 | 132 | #[test] 133 | fn should_parse_class_name() { 134 | let source_code = "public class SharpingClassVisitor { }"; 135 | 136 | let file = CSharpIdent::parse(source_code); 137 | assert_eq!(1, file.classes.len()); 138 | assert_eq!("SharpingClassVisitor", file.classes[0].name); 139 | } 140 | 141 | #[test] 142 | fn should_parse_class_impl_name() { 143 | let source_code = "public class SharpingClassVisitor: CSharpSyntaxWalker, DemoInterface { }"; 144 | 145 | let file = CSharpIdent::parse(source_code); 146 | assert_eq!(1, file.classes.len()); 147 | 148 | assert_eq!(2, file.classes[0].implements.len()); 149 | assert_eq!("CSharpSyntaxWalker", file.classes[0].implements[0]); 150 | assert_eq!("DemoInterface", file.classes[0].implements[1]); 151 | } 152 | 153 | #[test] 154 | fn should_parse_class_functions() { 155 | let source_code = "[ApiController] 156 | public class SharpingClassVisitor { 157 | public Domain domain { get; set; } 158 | public void VisitClassDeclaration(ClassDeclarationSyntax node){ 159 | 160 | } 161 | }"; 162 | 163 | let file = CSharpIdent::parse(source_code); 164 | assert_eq!(1, file.classes.len()); 165 | 166 | assert_eq!(1, file.classes[0].functions.len()); 167 | assert_eq!("VisitClassDeclaration", file.classes[0].functions[0].name); 168 | } 169 | } -------------------------------------------------------------------------------- /guarding_ident/src/identify/rust_ident.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use tree_sitter::{Node, Parser, Query, QueryCursor}; 4 | 5 | use guarding_core::domain::code_file::CodeFile; 6 | use guarding_core::domain::code_function::CodeFunction; 7 | use guarding_core::domain::code_class::CodeClass; 8 | use crate::code_ident::CodeIdent; 9 | 10 | const RUST_QUERY: &'static str = " 11 | (use_declaration 12 | (scoped_identifier) @import-name) 13 | 14 | (struct_item 15 | name: (type_identifier) @struct-name 16 | body: (field_declaration_list 17 | (field_declaration 18 | name: (field_identifier) @field-name 19 | type: (type_identifier) @type-name 20 | ))? 21 | ) 22 | 23 | (impl_item 24 | trait: (type_identifier)? @trait-name 25 | type: (type_identifier) @impl-struct-name 26 | body: (declaration_list ( 27 | (function_item 28 | name: (identifier) @impl-function-name 29 | )) 30 | ) 31 | ) 32 | "; 33 | 34 | 35 | pub struct RustIdent { 36 | parser: Parser, 37 | query: Query 38 | } 39 | 40 | impl RustIdent { 41 | fn new() -> RustIdent { 42 | let mut parser = Parser::new(); 43 | 44 | let language = tree_sitter_rust::language(); 45 | parser.set_language(language).unwrap(); 46 | 47 | let query = Query::new(language, &RUST_QUERY) 48 | .map_err(|e| println!("{}", format!("Query compilation failed: {:?}", e))).unwrap(); 49 | RustIdent { parser, query } 50 | } 51 | } 52 | 53 | impl RustIdent { 54 | fn do_parse(code: &str, ident: &mut RustIdent) -> CodeFile { 55 | let text_callback = |n: Node| &code[n.byte_range()]; 56 | let tree = ident.parser.parse(code, None).unwrap(); 57 | 58 | let mut query_cursor = QueryCursor::new(); 59 | let captures = query_cursor.captures(&ident.query, tree.root_node(), text_callback); 60 | 61 | let mut code_file = CodeFile::default(); 62 | let mut last_class_end_line = 0; 63 | let mut class = CodeClass::default(); 64 | 65 | let mut last_impl_struct_name = "".to_string(); 66 | let mut last_trait_name = "".to_string(); 67 | let mut impl_functions: HashMap> = Default::default(); 68 | let mut trait_struct_map: HashMap = Default::default(); 69 | 70 | for (mat, capture_index) in captures { 71 | let capture = mat.captures[capture_index]; 72 | let capture_name = &ident.query.capture_names()[capture.index as usize]; 73 | 74 | let text = capture.node.utf8_text((&code).as_ref()).unwrap_or(""); 75 | match capture_name.as_str() { 76 | "import-name" => { 77 | code_file.imports.push(text.to_string()); 78 | }, 79 | "struct-name" => { 80 | class.name = text.to_string(); 81 | let struct_node = capture.node; 82 | last_class_end_line = struct_node.end_position().row; 83 | RustIdent::insert_location(&mut class, struct_node); 84 | }, 85 | "impl-struct-name" => { 86 | last_impl_struct_name = text.to_string(); 87 | if last_trait_name != "" { 88 | trait_struct_map.insert(text.to_string(), last_trait_name); 89 | } 90 | last_trait_name = "".to_string(); 91 | } 92 | "impl-function-name" => { 93 | let function = RustIdent::create_function(capture, text); 94 | impl_functions 95 | .entry(last_impl_struct_name.clone()) 96 | .or_insert_with(Vec::new) 97 | .push(function); 98 | } 99 | "trait-name" => { 100 | last_trait_name = text.to_string(); 101 | } 102 | &_ => { 103 | 104 | } 105 | } 106 | 107 | if capture.node.start_position().row >= last_class_end_line { 108 | if !class.name.is_empty() { 109 | code_file.classes.push(class.clone()); 110 | class = CodeClass::default(); 111 | } 112 | } 113 | } 114 | 115 | for clz in code_file.classes.iter_mut() { 116 | if let Some(function) = impl_functions.get(clz.name.as_str()) { 117 | clz.functions = function.clone(); 118 | } 119 | 120 | if let Some(trait_name) = trait_struct_map.get(clz.name.as_str()) { 121 | clz.implements.push(trait_name.to_string()); 122 | } 123 | } 124 | 125 | code_file 126 | } 127 | } 128 | 129 | impl CodeIdent for RustIdent { 130 | fn parse(code: &str) -> CodeFile { 131 | let mut ident = RustIdent::new(); 132 | RustIdent::do_parse(code, &mut ident) 133 | } 134 | } 135 | 136 | #[cfg(test)] 137 | mod tests { 138 | use crate::code_ident::CodeIdent; 139 | use crate::rust_ident::RustIdent; 140 | 141 | #[test] 142 | fn should_parse_import() { 143 | let source_code = "use crate::identify::rust_ident::RustIdent; 144 | "; 145 | let file = RustIdent::parse(source_code); 146 | assert_eq!(1, file.imports.len()); 147 | } 148 | 149 | #[test] 150 | fn should_parse_basic_struct() { 151 | let source_code = "pub struct RustIdent { 152 | } 153 | "; 154 | let file = RustIdent::parse(source_code); 155 | assert_eq!(1, file.classes.len()); 156 | } 157 | 158 | #[test] 159 | fn should_parse_struct() { 160 | let source_code = "pub struct RustIdent {} 161 | 162 | impl RustIdent { 163 | pub fn parse(code: &str) -> CodeFile { 164 | CodeFile::default() 165 | } 166 | } 167 | "; 168 | let file = RustIdent::parse(source_code); 169 | 170 | assert_eq!(1, file.classes.len()); 171 | assert_eq!("RustIdent", file.classes[0].name); 172 | let functions = &file.classes[0].functions; 173 | assert_eq!(1, functions.len()); 174 | assert_eq!("parse", functions[0].name); 175 | } 176 | 177 | #[test] 178 | fn should_parse_for_trait_impl() { 179 | let source_code = "pub struct RustIdent {} 180 | 181 | impl Default for RustIdent { 182 | fn default() -> Self { 183 | RustIdent {} 184 | } 185 | } 186 | "; 187 | let file = RustIdent::parse(source_code); 188 | 189 | assert_eq!("Default", file.classes[0].implements[0]); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /guarding_ident/src/identify/java_ident.rs: -------------------------------------------------------------------------------- 1 | use tree_sitter::{Node, Parser, Query, QueryCursor}; 2 | 3 | use guarding_core::domain::code_file::CodeFile; 4 | use guarding_core::domain::code_class::CodeClass; 5 | use crate::code_ident::CodeIdent; 6 | 7 | const JAVA_QUERY: &'static str = " 8 | (package_declaration 9 | (scoped_identifier) @package-name) 10 | 11 | (import_declaration 12 | (scoped_identifier) @import-name) 13 | 14 | (method_declaration 15 | (modifiers 16 | (annotation 17 | name: (identifier) @annotation.name 18 | arguments: (annotation_argument_list)? @annotation.key_values 19 | ) 20 | ) 21 | ) 22 | 23 | (program 24 | (class_declaration 25 | name: (identifier) @class-name 26 | interfaces: (super_interfaces (interface_type_list (type_identifier) @impl-name))? 27 | ) 28 | ) 29 | 30 | "; 31 | 32 | pub struct JavaIdent { 33 | parser: Parser, 34 | query: Query 35 | } 36 | 37 | impl JavaIdent { 38 | pub fn new() -> JavaIdent { 39 | let mut parser = Parser::new(); 40 | let language = tree_sitter_java::language(); 41 | parser.set_language(language).unwrap(); 42 | 43 | let query = Query::new(language, &JAVA_QUERY) 44 | .map_err(|e| println!("{}", format!("Query compilation failed: {:?}", e))).unwrap(); 45 | 46 | JavaIdent { 47 | parser, 48 | query 49 | } 50 | } 51 | } 52 | 53 | impl JavaIdent { 54 | fn do_parse(code: &&str, ident: &mut JavaIdent) -> CodeFile { 55 | let tree = ident.parser.parse(code, None).unwrap(); 56 | let text_callback = |n: Node| &code[n.byte_range()]; 57 | let mut query_cursor = QueryCursor::new(); 58 | let captures = query_cursor.captures(&ident.query, tree.root_node(), text_callback); 59 | 60 | let mut code_file = CodeFile::default(); 61 | let mut class = CodeClass::default(); 62 | let mut is_last_node = false; 63 | 64 | let capture_names = ident.query.capture_names(); 65 | 66 | for (mat, capture_index) in captures { 67 | let capture = mat.captures[capture_index]; 68 | let capture_name = &capture_names[capture.index as usize]; 69 | 70 | let text = capture.node.utf8_text((&code).as_ref()).unwrap_or(""); 71 | match capture_name.as_str() { 72 | "package-name" => { 73 | code_file.package = text.to_string(); 74 | } 75 | "import-name" => { 76 | code_file.imports.push(text.to_string()); 77 | } 78 | "class-name" => { 79 | if !class.name.is_empty() { 80 | code_file.classes.push(class.clone()); 81 | class = CodeClass::default(); 82 | } 83 | 84 | class.name = text.to_string(); 85 | class.package = code_file.package.clone(); 86 | 87 | let class_node = capture.node.parent().unwrap(); 88 | JavaIdent::insert_location(&mut class, class_node); 89 | if !is_last_node { 90 | is_last_node = true; 91 | } 92 | } 93 | "impl-name" => { 94 | class.implements.push(text.to_string()); 95 | } 96 | "parameter" => {} 97 | &_ => { 98 | println!( 99 | " pattern: {}, capture: {}, row: {}, text: {:?}", 100 | mat.pattern_index, 101 | capture_name, 102 | capture.node.start_position().row, 103 | capture.node.utf8_text((&code).as_ref()).unwrap_or("") 104 | ); 105 | } 106 | } 107 | } 108 | 109 | if is_last_node { 110 | code_file.classes.push(class.clone()); 111 | } 112 | 113 | code_file 114 | } 115 | } 116 | 117 | impl CodeIdent for JavaIdent { 118 | fn parse(code: &str) -> CodeFile { 119 | let mut ident = JavaIdent::new(); 120 | JavaIdent::do_parse(&code, &mut ident) 121 | } 122 | } 123 | 124 | 125 | #[cfg(test)] 126 | mod tests { 127 | use crate::code_ident::CodeIdent; 128 | use crate::java_ident::JavaIdent; 129 | 130 | #[test] 131 | fn should_parse_import() { 132 | let source_code = "import java.lang.System; 133 | import java.io.InputStream; 134 | import payroll.Employee; 135 | "; 136 | let file = JavaIdent::parse(source_code); 137 | assert_eq!(3, file.imports.len()); 138 | } 139 | 140 | #[test] 141 | fn should_parse_impl_java_class() { 142 | let source_code = "class DateTimeImpl implements DateTime { 143 | @Override 144 | public Date getDate() { 145 | return new Date(); 146 | } 147 | }"; 148 | let file = JavaIdent::parse(source_code); 149 | assert_eq!(1, file.classes.len()); 150 | assert_eq!("DateTimeImpl", file.classes[0].name); 151 | assert_eq!(1, file.classes[0].implements.len()); 152 | assert_eq!("DateTime", file.classes[0].implements[0]); 153 | } 154 | 155 | #[test] 156 | fn should_parse_normal_java_class() { 157 | let source_code = "class DateTimeImpl { 158 | public Date getDate() { 159 | return new Date(); 160 | } 161 | }"; 162 | let file = JavaIdent::parse(source_code); 163 | assert_eq!(1, file.classes.len()); 164 | assert_eq!("DateTimeImpl", file.classes[0].name); 165 | } 166 | 167 | #[test] 168 | fn should_parse_multiple_java_class() { 169 | let source_code = "class DateTimeImpl { 170 | } 171 | 172 | class DateTimeImpl2 { 173 | } 174 | "; 175 | let file = JavaIdent::parse(source_code); 176 | assert_eq!(2, file.classes.len()); 177 | assert_eq!("DateTimeImpl", file.classes[0].name); 178 | assert_eq!("DateTimeImpl2", file.classes[1].name); 179 | } 180 | 181 | #[test] 182 | fn should_support_package_name() { 183 | let source_code = "package com.phodal.pepper.powermock; 184 | "; 185 | let file = JavaIdent::parse(source_code); 186 | assert_eq!("com.phodal.pepper.powermock", file.package); 187 | } 188 | 189 | #[test] 190 | fn should_support_inner_class() { 191 | let source_code = "class OuterClass { 192 | int x = 10; 193 | 194 | class InnerClass { 195 | int y = 5; 196 | } 197 | }"; 198 | 199 | let file = JavaIdent::parse(source_code); 200 | assert_eq!(1, file.classes.len()); 201 | } 202 | 203 | #[ignore] 204 | #[test] 205 | fn should_support_annotation() { 206 | let source_code = "public class HelloController { 207 | @RequestMapping(value = \"/ex/foos\", method = RequestMethod.GET) 208 | @ResponseBody 209 | public String getFoosBySimplePath() { 210 | return \"Get some Foos\"; 211 | } 212 | }"; 213 | 214 | let file = JavaIdent::parse(source_code); 215 | println!("{:?}", file); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /docs/guarding-process.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 3 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Guarding Rules 21 | 22 | 23 | Source Code 24 | 25 | 26 | Tree sitter 27 | 28 | 29 | Guarding Prarser 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | S-expressions 59 | 60 | 61 | 62 | 63 | Executor 64 | 65 | 66 | 67 | 68 | 69 | Results 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to coco 2 | 3 | We would love for you to contribute to coco and help make it even better than it is 4 | today! As a contributor, here are the guidelines we would like you to follow: 5 | 6 | - [Code of Conduct](#coc) 7 | - [Question or Problem?](#question) 8 | - [Issues and Bugs](#issue) 9 | - [Feature Requests](#feature) 10 | - [Submission Guidelines](#submit) 11 | - [Coding Rules](#rules) 12 | - [Commit Message Guidelines](#commit) 13 | 14 | ## Code of Conduct 15 | 16 | Help us keep coco open and inclusive. Please read and follow our [Code of Conduct][coc]. 17 | 18 | ## Got a Question or Problem? 19 | 20 | Do not open issues for general support questions as we want to keep GitHub issues for bug reports and feature requests. 21 | 22 | Segmentfault / Stack Overflow is a much better place to ask questions since: 23 | 24 | - there are thousands of people willing to help on Segmentfault 25 | - questions and answers stay available for public viewing so your question / answer might help someone else 26 | - Segmentfault's voting system assures that the best answers are prominently visible. 27 | 28 | To save your and our time, we will systematically close all issues that are requests for general support and redirect people to Segmentfault / Stack Overflow. 29 | 30 | ## Found a Bug? 31 | If you find a bug in the source code, you can help us by 32 | [submitting an issue](#submit-issue) to our [GitHub Repository][github]. Even better, you can 33 | [submit a Pull Request](#submit-pr) with a fix. 34 | 35 | ## Missing a Feature? 36 | You can *request* a new feature by [submitting an issue](#submit-issue) to our GitHub 37 | Repository. If you would like to *implement* a new feature, please submit an issue with 38 | a for your work first, to be sure that we can use it. 39 | Please consider what kind of change it is: 40 | 41 | * For a **Major Feature**, first open an issue and outline your proposal so that it can be 42 | discussed. This will also allow us to better coordinate our efforts, prevent duplication of work, 43 | and help you to craft the change so that it is successfully accepted into the project. 44 | * **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). 45 | 46 | ## Submission Guidelines 47 | 48 | ### Submitting an Issue 49 | 50 | Before you submit an issue, please search the issue tracker, maybe an issue for your problem already exists and the discussion might inform you of workarounds readily available. 51 | 52 | We want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. In order to reproduce bugs we will systematically ask you to provide a minimal reproduction scenario using http://plnkr.co. Having a live, reproducible scenario gives us wealth of important information without going back & forth to you with additional questions like: 53 | 54 | - version of coco used 55 | - 3rd-party libraries and their versions 56 | - and most importantly - a use-case that fails 57 | 58 | A minimal reproduce scenario using http://plnkr.co/ allows us to quickly confirm a bug (or point out coding problem) as well as confirm that we are fixing the right problem. If plunker is not a suitable way to demonstrate the problem (for example for issues related to our npm packaging), please create a standalone git repository demonstrating the problem. 59 | 60 | We will be insisting on a minimal reproduce scenario in order to save maintainers time and ultimately be able to fix more bugs. Interestingly, from our experience users often find coding problems themselves while preparing a minimal plunk. We understand that sometimes it might be hard to extract essentials bits of code from a larger code-base but we really need to isolate the problem before we can fix it. 61 | 62 | Unfortunately we are not able to investigate / fix bugs without a minimal reproduction, so if we don't hear back from you we are going to close an issue that don't have enough info to be reproduced. 63 | 64 | You can file new issues by filling out our [new issue form](https://github.com/inherd/coco/issues/new). 65 | 66 | 67 | ### Submitting a Pull Request (PR) 68 | Before you submit your Pull Request (PR) consider the following guidelines: 69 | 70 | * Search [GitHub](https://github.com/inherd/coco/pulls) for an open or closed PR 71 | that relates to your submission. You don't want to duplicate effort. 72 | * Make your changes in a new git branch: 73 | 74 | ```shell 75 | git checkout -b my-fix-branch master 76 | ``` 77 | 78 | * Create your patch, **including appropriate test cases**. 79 | * Follow our [Coding Rules](#rules). 80 | * Run the full coco test suite, and ensure that all tests pass. 81 | * Commit your changes using a descriptive commit message that follows our 82 | [commit message conventions](#commit). Adherence to these conventions 83 | is necessary because release notes are automatically generated from these messages. 84 | 85 | ```shell 86 | git commit -a 87 | ``` 88 | Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files. 89 | 90 | * Push your branch to GitHub: 91 | 92 | ```shell 93 | git push origin my-fix-branch 94 | ``` 95 | 96 | * In GitHub, send a pull request to `coco:master`. 97 | * If we suggest changes then: 98 | * Make the required updates. 99 | * Re-run the coco test suites to ensure tests are still passing. 100 | * Rebase your branch and force push to your GitHub repository (this will update your Pull Request): 101 | 102 | ```shell 103 | git rebase master -i 104 | git push -f 105 | ``` 106 | 107 | That's it! Thank you for your contribution! 108 | 109 | #### After your pull request is merged 110 | 111 | After your pull request is merged, you can safely delete your branch and pull the changes 112 | from the main (upstream) repository: 113 | 114 | * Delete the remote branch on GitHub either through the GitHub web UI or your local shell as follows: 115 | 116 | ```shell 117 | git push origin --delete my-fix-branch 118 | ``` 119 | 120 | * Check out the master branch: 121 | 122 | ```shell 123 | git checkout master -f 124 | ``` 125 | 126 | * Delete the local branch: 127 | 128 | ```shell 129 | git branch -D my-fix-branch 130 | ``` 131 | 132 | * Update your master with the latest upstream version: 133 | 134 | ```shell 135 | git pull --ff upstream master 136 | ``` 137 | 138 | ## Coding Rules 139 | To ensure consistency throughout the source code, keep these rules in mind as you are working: 140 | 141 | * All features or bug fixes **must be tested** by one or more specs (unit-tests). 142 | * All public API methods **must be documented**. 143 | 144 | ## Commit Message Guidelines 145 | 146 | We have very precise rules over how our git commit messages can be formatted. This leads to **more 147 | readable messages** that are easy to follow when looking through the **project history**. But also, 148 | we use the git commit messages to **generate the coco change log**. 149 | 150 | ### Commit Message Format 151 | Each commit message consists of a **header**, a **body** and a **footer**. The header has a special 152 | format that includes a **type**, a **scope** and a **subject**: 153 | 154 | ``` 155 | (): 156 | 157 | 158 | 159 |