diff --git a/libs/ast-extractor/.idea/.gitignore b/libs/ast-extractor/.idea/.gitignore
new file mode 100644
index 00000000..13566b81
--- /dev/null
+++ b/libs/ast-extractor/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/libs/ast-extractor/.idea/vcs.xml b/libs/ast-extractor/.idea/vcs.xml
new file mode 100644
index 00000000..b2bdec2d
--- /dev/null
+++ b/libs/ast-extractor/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/toolchains/solidity/linter/core/solidhunter-lib/src/linter.rs b/toolchains/solidity/linter/core/solidhunter-lib/src/linter.rs
index ec431199..cf831efc 100644
--- a/toolchains/solidity/linter/core/solidhunter-lib/src/linter.rs
+++ b/toolchains/solidity/linter/core/solidhunter-lib/src/linter.rs
@@ -8,7 +8,7 @@ use std::fs;
use glob::glob;
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub struct SolidFile {
pub data: ast_extractor::File,
pub path: String,
diff --git a/toolchains/solidity/linter/core/solidhunter-lib/src/rules/order/mod.rs b/toolchains/solidity/linter/core/solidhunter-lib/src/rules/order/mod.rs
index a22ad358..cc0c65c1 100644
--- a/toolchains/solidity/linter/core/solidhunter-lib/src/rules/order/mod.rs
+++ b/toolchains/solidity/linter/core/solidhunter-lib/src/rules/order/mod.rs
@@ -3,19 +3,22 @@ use std::collections::HashMap;
#[macro_use]
pub(crate) mod import_on_top;
+pub(crate) mod ordering;
// List all rules
use crate::rules::order::import_on_top::ImportOnTop;
+use crate::rules::order::ordering::Ordering;
use crate::rules::RuleBuilder;
pub fn create_default_rules() -> Vec {
- vec![ImportOnTop::create_default()]
+ vec![ImportOnTop::create_default(), Ordering::create_default()]
}
pub fn create_rules() -> RulesMap {
let mut rules: HashMap = HashMap::new();
rules.insert(import_on_top::RULE_ID.to_string(), ImportOnTop::create);
+ rules.insert(ordering::RULE_ID.to_string(), Ordering::create);
rules
}
diff --git a/toolchains/solidity/linter/core/solidhunter-lib/src/rules/order/ordering.rs b/toolchains/solidity/linter/core/solidhunter-lib/src/rules/order/ordering.rs
new file mode 100644
index 00000000..c4a67c95
--- /dev/null
+++ b/toolchains/solidity/linter/core/solidhunter-lib/src/rules/order/ordering.rs
@@ -0,0 +1,504 @@
+use std::collections::HashMap;
+
+use ast_extractor::{Spanned, Visit, visit, FunctionKind, Visibility};
+
+use crate::linter::SolidFile;
+use crate::rules::types::*;
+use crate::types::*;
+
+pub const RULE_ID: &str = "ordering";
+const MESSAGE: &str = "Import must be on top in the file";
+
+struct OrderingVisitor {
+ file: SolidFile,
+ data: RuleEntry,
+ authorized_file_items: HashMap>>,
+ authorized_contract_items: HashMap>>,
+ file_current_item: Option,
+ contract_current_item: Option,
+ inside_contract: bool,
+ reports: Vec,
+}
+
+impl OrderingVisitor {
+ fn new(file: SolidFile, data: RuleEntry) -> OrderingVisitor {
+ let authorized_file_items: HashMap>> = [
+ (FileItemType::Pragma, vec![None]),
+ (FileItemType::Import, vec![None, Some(FileItemType::Pragma)]),
+ (
+ FileItemType::Enum,
+ vec![
+ None,
+ Some(FileItemType::Pragma),
+ Some(FileItemType::Import),
+ ],
+ ),
+ (
+ FileItemType::Struct,
+ vec![
+ None,
+ Some(FileItemType::Pragma),
+ Some(FileItemType::Import),
+ Some(FileItemType::Enum),
+ ],
+ ),
+ (
+ FileItemType::ContractInterface,
+ vec![
+ None,
+ Some(FileItemType::Pragma),
+ Some(FileItemType::Import),
+ Some(FileItemType::Enum),
+ Some(FileItemType::Struct),
+ ],
+ ),
+ (
+ FileItemType::ContractLibrary,
+ vec![
+ None,
+ Some(FileItemType::Pragma),
+ Some(FileItemType::Import),
+ Some(FileItemType::Enum),
+ Some(FileItemType::Struct),
+ Some(FileItemType::ContractInterface),
+ ],
+ ),
+ (
+ FileItemType::Contract,
+ vec![
+ None,
+ Some(FileItemType::Pragma),
+ Some(FileItemType::Import),
+ Some(FileItemType::Enum),
+ Some(FileItemType::Struct),
+ Some(FileItemType::ContractInterface),
+ Some(FileItemType::ContractLibrary),
+ ],
+ ),
+ ].iter()
+ .cloned()
+ .collect();
+
+ let authorized_contract_items: HashMap>> = [
+ (ContractItemType::Udt, vec![None]),
+ (
+ ContractItemType::Struct,
+ vec![None, Some(ContractItemType::Udt)],
+ ),
+ (
+ ContractItemType::Enum,
+ vec![None, Some(ContractItemType::Udt), Some(ContractItemType::Struct)],
+ ),
+ (
+ ContractItemType::Property,
+ vec![
+ None,
+ Some(ContractItemType::Udt),
+ Some(ContractItemType::Struct),
+ Some(ContractItemType::Enum),
+ ],
+ ),
+ (
+ ContractItemType::Event,
+ vec![
+ None,
+ Some(ContractItemType::Udt),
+ Some(ContractItemType::Struct),
+ Some(ContractItemType::Enum),
+ Some(ContractItemType::Property),
+ ],
+ ),
+ (
+ ContractItemType::Modifier,
+ vec![
+ None,
+ Some(ContractItemType::Udt),
+ Some(ContractItemType::Struct),
+ Some(ContractItemType::Enum),
+ Some(ContractItemType::Property),
+ Some(ContractItemType::Event),
+ ],
+ ),
+ (
+ ContractItemType::Constructor,
+ vec![
+ None,
+ Some(ContractItemType::Udt),
+ Some(ContractItemType::Struct),
+ Some(ContractItemType::Enum),
+ Some(ContractItemType::Property),
+ Some(ContractItemType::Event),
+ Some(ContractItemType::Modifier),
+ ],
+ ),
+ (
+ ContractItemType::Receive,
+ vec![
+ None,
+ Some(ContractItemType::Udt),
+ Some(ContractItemType::Struct),
+ Some(ContractItemType::Enum),
+ Some(ContractItemType::Property),
+ Some(ContractItemType::Event),
+ Some(ContractItemType::Modifier),
+ Some(ContractItemType::Constructor),
+ ],
+ ),
+ (
+ ContractItemType::FallBack,
+ vec![
+ None,
+ Some(ContractItemType::Udt),
+ Some(ContractItemType::Struct),
+ Some(ContractItemType::Enum),
+ Some(ContractItemType::Property),
+ Some(ContractItemType::Event),
+ Some(ContractItemType::Modifier),
+ Some(ContractItemType::Constructor),
+ Some(ContractItemType::Receive),
+ ],
+ ),
+ (
+ ContractItemType::ExternalFunction,
+ vec![
+ None,
+ Some(ContractItemType::Udt),
+ Some(ContractItemType::Struct),
+ Some(ContractItemType::Enum),
+ Some(ContractItemType::Property),
+ Some(ContractItemType::Event),
+ Some(ContractItemType::Modifier),
+ Some(ContractItemType::Constructor),
+ Some(ContractItemType::Receive),
+ Some(ContractItemType::FallBack),
+ ],
+ ),
+ (
+ ContractItemType::PublicFunction,
+ vec![
+ None,
+ Some(ContractItemType::Udt),
+ Some(ContractItemType::Struct),
+ Some(ContractItemType::Enum),
+ Some(ContractItemType::Property),
+ Some(ContractItemType::Event),
+ Some(ContractItemType::Modifier),
+ Some(ContractItemType::Constructor),
+ Some(ContractItemType::Receive),
+ Some(ContractItemType::FallBack),
+ Some(ContractItemType::ExternalFunction),
+ ],
+ ),
+ (
+ ContractItemType::InternalFunction,
+ vec![
+ None,
+ Some(ContractItemType::Udt),
+ Some(ContractItemType::Struct),
+ Some(ContractItemType::Enum),
+ Some(ContractItemType::Property),
+ Some(ContractItemType::Event),
+ Some(ContractItemType::Modifier),
+ Some(ContractItemType::Constructor),
+ Some(ContractItemType::Receive),
+ Some(ContractItemType::FallBack),
+ Some(ContractItemType::ExternalFunction),
+ Some(ContractItemType::PublicFunction),
+ ],
+ ),
+ (
+ ContractItemType::PrivateFunction,
+ vec![
+ None,
+ Some(ContractItemType::Udt),
+ Some(ContractItemType::Struct),
+ Some(ContractItemType::Enum),
+ Some(ContractItemType::Property),
+ Some(ContractItemType::Event),
+ Some(ContractItemType::Modifier),
+ Some(ContractItemType::Constructor),
+ Some(ContractItemType::Receive),
+ Some(ContractItemType::FallBack),
+ Some(ContractItemType::ExternalFunction),
+ Some(ContractItemType::PublicFunction),
+ Some(ContractItemType::InternalFunction),
+ ],
+ ),
+ ].iter()
+ .cloned()
+ .collect();
+ OrderingVisitor {
+ file,
+ data,
+ authorized_file_items,
+ authorized_contract_items,
+ file_current_item: None,
+ contract_current_item: None,
+ inside_contract: false,
+ reports: Vec::new(),
+ }
+ }
+
+ fn create_diag(
+ &self,
+ file: &SolidFile,
+ location: (ast_extractor::LineColumn, ast_extractor::LineColumn),
+ ) -> LintDiag {
+ let range = Range {
+ start: Position {
+ line: location.0.line,
+ character: location.0.column,
+ },
+ end: Position {
+ line: location.1.line,
+ character: location.1.column,
+ },
+ };
+ LintDiag {
+ id: RULE_ID.to_string(),
+ range,
+ message: MESSAGE.to_string(),
+ severity: Some(self.data.severity),
+ code: None,
+ source: None,
+ uri: file.path.clone(),
+ source_file_content: file.content.clone(),
+ }
+ }
+
+ fn is_authorized_file_item(&self, item: FileItemType) -> bool {
+ if let Some(authorized_items) = self.authorized_file_items.get(&item) {
+ let res = authorized_items.contains(&self.file_current_item);
+ return res;
+ }
+ true
+ }
+
+ fn is_authorized_contract_item(&self, item: ContractItemType) -> bool {
+ if let Some(authorized_items) = self.authorized_contract_items.get(&item) {
+ return authorized_items.contains(&self.contract_current_item);
+ }
+ true
+ }
+}
+
+impl<'ast> Visit<'ast> for OrderingVisitor {
+ fn visit_pragma_directive(&mut self, pragma: &'ast ast_extractor::PragmaDirective) {
+ if !self.is_authorized_file_item(FileItemType::Pragma) {
+ let location = (pragma.span().start(), pragma.span().end());
+ self.reports.push(self.create_diag(&self.file, location));
+ } else {
+ self.file_current_item = Some(FileItemType::Pragma);
+ }
+ }
+
+ fn visit_import_directive(&mut self, import: &'ast ast_extractor::ImportDirective) {
+ if !self.is_authorized_file_item(FileItemType::Import) {
+ let location = (import.span().start(), import.span().end());
+ self.reports.push(self.create_diag(&self.file, location));
+ } else {
+ self.file_current_item = Some(FileItemType::Import);
+ }
+ }
+
+ fn visit_item_enum(&mut self, enum_def: &'ast ast_extractor::ItemEnum) {
+ if !self.is_authorized_file_item(FileItemType::Enum) {
+ let location = (enum_def.span().start(), enum_def.span().end());
+ self.reports.push(self.create_diag(&self.file, location));
+ } else {
+ self.file_current_item = Some(FileItemType::Enum);
+ }
+ }
+
+ fn visit_item_struct(&mut self, struct_def: &'ast ast_extractor::ItemStruct) {
+ if !self.is_authorized_file_item(FileItemType::Struct) {
+ let location = (struct_def.span().start(), struct_def.span().end());
+ self.reports.push(self.create_diag(&self.file, location));
+ } else {
+ self.file_current_item = Some(FileItemType::Struct);
+ }
+ }
+
+ fn visit_item_contract(&mut self, contract_def: &'ast ast_extractor::ItemContract) {
+ if contract_def.is_interface() && !self.is_authorized_file_item(FileItemType::ContractInterface) ||
+ contract_def.is_library() && !self.is_authorized_file_item(FileItemType::ContractLibrary) ||
+ !self.is_authorized_file_item(FileItemType::Contract) {
+ let location = (contract_def.span().start(), contract_def.span().end());
+ self.reports.push(self.create_diag(&self.file, location));
+ } else {
+ self.file_current_item = Some(FileItemType::Contract);
+ }
+ self.contract_current_item = None;
+ self.inside_contract = true;
+ visit::visit_item_contract(self, contract_def);
+ self.inside_contract = false;
+ }
+
+ fn visit_item_udt(&mut self,udt: &'ast ast_extractor::ItemUdt) {
+ if !self.is_authorized_contract_item(ContractItemType::Udt) {
+ let location = (udt.span().start(), udt.span().end());
+ self.reports.push(self.create_diag(&self.file, location));
+ } else {
+ self.contract_current_item = Some(ContractItemType::Udt);
+ }
+ }
+
+ fn visit_variable_definition(&mut self,var: &'ast ast_extractor::VariableDefinition) {
+ if !self.is_authorized_contract_item(ContractItemType::Property) {
+ let location = (var.span().start(), var.span().end());
+ self.reports.push(self.create_diag(&self.file, location));
+ } else {
+ self.contract_current_item = Some(ContractItemType::Property);
+ }
+ }
+
+ fn visit_item_event(&mut self,event: &'ast ast_extractor::ItemEvent) {
+ if !self.is_authorized_contract_item(ContractItemType::Event) {
+ let location = (event.span().start(), event.span().end());
+ self.reports.push(self.create_diag(&self.file, location));
+ } else {
+ self.contract_current_item = Some(ContractItemType::Event);
+ }
+ }
+
+ fn visit_item_function(&mut self,function: &'ast ast_extractor::ItemFunction) {
+
+ match function.kind {
+ FunctionKind::Modifier(_) => {
+ if !self.is_authorized_contract_item(ContractItemType::Modifier) {
+ let location = (function.span().start(), function.span().end());
+ self.reports.push(self.create_diag(&self.file, location));
+ } else {
+ self.contract_current_item = Some(ContractItemType::Modifier);
+ }
+ },
+ FunctionKind::Constructor(_) => {
+ if !self.is_authorized_contract_item(ContractItemType::Constructor) {
+ let location = (function.span().start(), function.span().end());
+ self.reports.push(self.create_diag(&self.file, location));
+ } else {
+ self.contract_current_item = Some(ContractItemType::Constructor);
+ }
+ },
+ FunctionKind::Receive(_) => {
+ if !self.is_authorized_contract_item(ContractItemType::Receive) {
+ let location = (function.span().start(), function.span().end());
+ self.reports.push(self.create_diag(&self.file, location));
+ } else {
+ self.contract_current_item = Some(ContractItemType::Receive);
+ }
+ },
+ FunctionKind::Fallback(_) => {
+ if !self.is_authorized_contract_item(ContractItemType::FallBack) {
+ let location = (function.span().start(), function.span().end());
+ self.reports.push(self.create_diag(&self.file, location));
+ } else {
+ self.contract_current_item = Some(ContractItemType::FallBack);
+ }
+ },
+ FunctionKind::Function(_) => {
+ let visibility = function.attributes.iter().find(|attr| matches!(attr, ast_extractor::FunctionAttribute::Visibility(_)));
+ let visibility = match visibility {
+ Some(ast_extractor::FunctionAttribute::Visibility(visibility)) => visibility,
+ _ => return,
+ };
+
+ match visibility {
+ Visibility::External(_) => {
+ if !self.is_authorized_contract_item(ContractItemType::ExternalFunction) {
+ let location = (function.span().start(), function.span().end());
+ self.reports.push(self.create_diag(&self.file, location));
+ } else {
+ self.contract_current_item = Some(ContractItemType::ExternalFunction);
+ }
+ },
+ Visibility::Public(_) => {
+ if !self.is_authorized_contract_item(ContractItemType::PublicFunction) {
+ let location = (function.span().start(), function.span().end());
+ self.reports.push(self.create_diag(&self.file, location));
+ } else {
+ self.contract_current_item = Some(ContractItemType::PublicFunction);
+ }
+ },
+ Visibility::Internal(_) => {
+ if !self.is_authorized_contract_item(ContractItemType::InternalFunction) {
+ let location = (function.span().start(), function.span().end());
+ self.reports.push(self.create_diag(&self.file, location));
+ } else {
+ self.contract_current_item = Some(ContractItemType::InternalFunction);
+ }
+ },
+ Visibility::Private(_) => {
+ if !self.is_authorized_contract_item(ContractItemType::PrivateFunction) {
+ let location = (function.span().start(), function.span().end());
+ self.reports.push(self.create_diag(&self.file, location));
+ } else {
+ self.contract_current_item = Some(ContractItemType::PrivateFunction);
+ }
+ },
+ }
+
+ }
+ }
+ }
+
+
+}
+
+#[derive(Debug, Clone)]
+pub struct Ordering {
+ data: RuleEntry,
+}
+
+
+impl RuleType for Ordering {
+ fn diagnose(&self, file: &SolidFile, _files: &[SolidFile]) -> Vec {
+ let mut visitor = OrderingVisitor::new(file.clone(), self.data.clone());
+ visitor.visit_file(&file.data);
+ visitor.reports
+ }
+}
+
+impl Ordering {
+ pub(crate) fn create(data: RuleEntry) -> Box {
+ let rule = Ordering { data };
+ Box::new(rule)
+ }
+
+ pub(crate) fn create_default() -> RuleEntry {
+ RuleEntry {
+ id: RULE_ID.to_string(),
+ severity: Severity::WARNING,
+ data: vec![],
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+enum FileItemType {
+ Pragma,
+ Import,
+ Enum,
+ Struct,
+ ContractInterface,
+ ContractLibrary,
+ Contract,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+enum ContractItemType {
+ Udt,
+ Struct,
+ Enum,
+ Property,
+ Event,
+ Modifier,
+ Constructor,
+ Receive,
+ FallBack,
+ ExternalFunction,
+ PublicFunction,
+ InternalFunction,
+ PrivateFunction,
+}
+
diff --git a/toolchains/solidity/linter/core/solidhunter-lib/src/rules/types.rs b/toolchains/solidity/linter/core/solidhunter-lib/src/rules/types.rs
index f18dedcf..08d0c0f9 100644
--- a/toolchains/solidity/linter/core/solidhunter-lib/src/rules/types.rs
+++ b/toolchains/solidity/linter/core/solidhunter-lib/src/rules/types.rs
@@ -3,7 +3,7 @@ use crate::types::*;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
-#[derive(Serialize, Deserialize, Debug)]
+#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RuleEntry {
pub id: String,
pub severity: Severity,
diff --git a/toolchains/solidity/linter/core/solidhunter-lib/testdata/Ordering/.solidhunter.json b/toolchains/solidity/linter/core/solidhunter-lib/testdata/Ordering/.solidhunter.json
new file mode 100644
index 00000000..8cd1f7d2
--- /dev/null
+++ b/toolchains/solidity/linter/core/solidhunter-lib/testdata/Ordering/.solidhunter.json
@@ -0,0 +1,12 @@
+{
+ "name": "solidhunter",
+ "includes": [],
+ "plugins": [],
+ "rules": [
+ {
+ "id": "ordering",
+ "severity": "WARNING",
+ "data": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/toolchains/solidity/linter/core/solidhunter-lib/testdata/Ordering/file.sol b/toolchains/solidity/linter/core/solidhunter-lib/testdata/Ordering/file.sol
new file mode 100644
index 00000000..f3584746
--- /dev/null
+++ b/toolchains/solidity/linter/core/solidhunter-lib/testdata/Ordering/file.sol
@@ -0,0 +1,12 @@
+pragma solidity 0.8.19;
+
+
+contract MyContract {
+ function foo() public {}
+
+ using MyMathLib for uint; // NOK
+
+ uint a; // NOK
+}
+
+library MyLibrary {} // NOK
\ No newline at end of file
diff --git a/toolchains/solidity/linter/core/solidhunter-lib/testdata/Ordering/findings.csv b/toolchains/solidity/linter/core/solidhunter-lib/testdata/Ordering/findings.csv
new file mode 100644
index 00000000..fb25cf75
--- /dev/null
+++ b/toolchains/solidity/linter/core/solidhunter-lib/testdata/Ordering/findings.csv
@@ -0,0 +1,2 @@
+ordering:9:4:9:11
+ordering:12:8:12:17
\ No newline at end of file
diff --git a/toolchains/solidity/linter/core/solidhunter-lib/tests/linter.rs b/toolchains/solidity/linter/core/solidhunter-lib/tests/linter.rs
index a123add4..1f129cd1 100644
--- a/toolchains/solidity/linter/core/solidhunter-lib/tests/linter.rs
+++ b/toolchains/solidity/linter/core/solidhunter-lib/tests/linter.rs
@@ -167,4 +167,5 @@ test_directories! {
GlobalImport,
NotRelyOnTime,
NamedParametersMapping,
+ Ordering
}