abap-parser 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {abap_parser-0.2.0 → abap_parser-0.3.0}/Cargo.lock +10 -9
- {abap_parser-0.2.0 → abap_parser-0.3.0}/Cargo.toml +1 -1
- {abap_parser-0.2.0 → abap_parser-0.3.0}/PKG-INFO +1 -1
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-analysis/src/calls.rs +56 -0
- abap_parser-0.3.0/crates/abap-analysis/src/enhance.rs +305 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-analysis/src/lib.rs +1 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-analysis/src/logic.rs +12 -2
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-analysis/src/text.rs +11 -2
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-analysis/src/workspace.rs +704 -85
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-model/src/lib.rs +4 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-source/Cargo.toml +1 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-source/src/adt.rs +377 -24
- abap_parser-0.3.0/crates/abap-source/src/badi.rs +218 -0
- abap_parser-0.3.0/crates/abap-source/src/cache.rs +268 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-source/src/fs.rs +141 -2
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-source/src/lib.rs +157 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/python/abap_parser/types.py +1 -1
- abap_parser-0.2.0/crates/abap-source/src/cache.rs +0 -126
- {abap_parser-0.2.0 → abap_parser-0.3.0}/LICENSE +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/README.md +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/bindings/python/Cargo.toml +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/bindings/python/LICENSE +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/bindings/python/README.md +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/bindings/python/src/lib.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/bindings/python/tests/test_symbols.py +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-analysis/Cargo.toml +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-analysis/src/args.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-analysis/src/context.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-analysis/src/db.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-analysis/src/ddic.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-analysis/src/stores.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-analysis/src/symbols.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-analysis/tests/adt_mock.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-analysis/tests/amdp_corpus.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-analysis/tests/call_graph_corpus.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-analysis/tests/corpus_smoke.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-analysis/tests/logic_corpus.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-analysis/tests/symbols_diff.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-lexer/Cargo.toml +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-lexer/src/lib.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-model/Cargo.toml +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-source/src/mock.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-source/src/options.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-sql/Cargo.toml +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-sql/src/cds.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-sql/src/hana.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-sql/src/lib.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-sql/src/tests.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-sql/tests/cds_corpus.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-sql/tests/sql_diff.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-syntax/Cargo.toml +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-syntax/src/classify.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-syntax/src/decl.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-syntax/src/expr.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-syntax/src/kind.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-syntax/src/lib.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-syntax/src/operands.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-syntax/src/structure.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-syntax/tests/abaplint_statements.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-syntax/tests/common/mod.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-syntax/tests/oracle_diff.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/crates/abap-syntax/tests/refs_diff.rs +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/pyproject.toml +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/python/abap_parser/__init__.py +0 -0
- {abap_parser-0.2.0 → abap_parser-0.3.0}/python/abap_parser/py.typed +0 -0
|
@@ -4,7 +4,7 @@ version = 4
|
|
|
4
4
|
|
|
5
5
|
[[package]]
|
|
6
6
|
name = "abap-analysis"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
dependencies = [
|
|
9
9
|
"abap-model",
|
|
10
10
|
"abap-source",
|
|
@@ -16,7 +16,7 @@ dependencies = [
|
|
|
16
16
|
|
|
17
17
|
[[package]]
|
|
18
18
|
name = "abap-cli"
|
|
19
|
-
version = "0.
|
|
19
|
+
version = "0.3.0"
|
|
20
20
|
dependencies = [
|
|
21
21
|
"abap-analysis",
|
|
22
22
|
"abap-lexer",
|
|
@@ -28,14 +28,14 @@ dependencies = [
|
|
|
28
28
|
|
|
29
29
|
[[package]]
|
|
30
30
|
name = "abap-lexer"
|
|
31
|
-
version = "0.
|
|
31
|
+
version = "0.3.0"
|
|
32
32
|
dependencies = [
|
|
33
33
|
"serde",
|
|
34
34
|
]
|
|
35
35
|
|
|
36
36
|
[[package]]
|
|
37
37
|
name = "abap-model"
|
|
38
|
-
version = "0.
|
|
38
|
+
version = "0.3.0"
|
|
39
39
|
dependencies = [
|
|
40
40
|
"schemars",
|
|
41
41
|
"serde",
|
|
@@ -44,7 +44,7 @@ dependencies = [
|
|
|
44
44
|
|
|
45
45
|
[[package]]
|
|
46
46
|
name = "abap-node"
|
|
47
|
-
version = "0.
|
|
47
|
+
version = "0.3.0"
|
|
48
48
|
dependencies = [
|
|
49
49
|
"abap-analysis",
|
|
50
50
|
"napi",
|
|
@@ -55,7 +55,7 @@ dependencies = [
|
|
|
55
55
|
|
|
56
56
|
[[package]]
|
|
57
57
|
name = "abap-python"
|
|
58
|
-
version = "0.
|
|
58
|
+
version = "0.3.0"
|
|
59
59
|
dependencies = [
|
|
60
60
|
"abap-analysis",
|
|
61
61
|
"pyo3",
|
|
@@ -63,16 +63,17 @@ dependencies = [
|
|
|
63
63
|
|
|
64
64
|
[[package]]
|
|
65
65
|
name = "abap-source"
|
|
66
|
-
version = "0.
|
|
66
|
+
version = "0.3.0"
|
|
67
67
|
dependencies = [
|
|
68
68
|
"base64 0.22.1",
|
|
69
69
|
"serde",
|
|
70
|
+
"serde_json",
|
|
70
71
|
"ureq",
|
|
71
72
|
]
|
|
72
73
|
|
|
73
74
|
[[package]]
|
|
74
75
|
name = "abap-sql"
|
|
75
|
-
version = "0.
|
|
76
|
+
version = "0.3.0"
|
|
76
77
|
dependencies = [
|
|
77
78
|
"abap-model",
|
|
78
79
|
"abap-syntax",
|
|
@@ -82,7 +83,7 @@ dependencies = [
|
|
|
82
83
|
|
|
83
84
|
[[package]]
|
|
84
85
|
name = "abap-syntax"
|
|
85
|
-
version = "0.
|
|
86
|
+
version = "0.3.0"
|
|
86
87
|
dependencies = [
|
|
87
88
|
"abap-lexer",
|
|
88
89
|
"serde",
|
|
@@ -3,7 +3,7 @@ resolver = "2"
|
|
|
3
3
|
members = ["crates/abap-lexer", "crates/abap-syntax", "crates/abap-model", "crates/abap-sql", "crates/abap-source", "crates/abap-analysis", "bindings/python"]
|
|
4
4
|
|
|
5
5
|
[workspace.package]
|
|
6
|
-
version = "0.
|
|
6
|
+
version = "0.3.0"
|
|
7
7
|
edition = "2021"
|
|
8
8
|
license = "MIT"
|
|
9
9
|
repository = "https://github.com/albertlast/abapPaser"
|
|
@@ -402,6 +402,18 @@ pub(crate) struct CallExtractor<'a> {
|
|
|
402
402
|
pub file: &'a File,
|
|
403
403
|
pub symbols: &'a m::FileSymbols,
|
|
404
404
|
pub resolver: &'a dyn TypeResolver,
|
|
405
|
+
/// main program, for the names of customer exits
|
|
406
|
+
pub program: Option<String>,
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/// The main program named by `REPORT`, `PROGRAM` or `FUNCTION-POOL`.
|
|
410
|
+
pub(crate) fn main_program(symbols: &m::FileSymbols) -> Option<String> {
|
|
411
|
+
let p = symbols.program.as_ref()?;
|
|
412
|
+
match p.kind.as_str() {
|
|
413
|
+
"report" | "program" => Some(p.name.to_ascii_uppercase()),
|
|
414
|
+
"function-pool" => Some(format!("SAPL{}", p.name.to_ascii_uppercase())),
|
|
415
|
+
_ => None,
|
|
416
|
+
}
|
|
405
417
|
}
|
|
406
418
|
|
|
407
419
|
impl<'a> CallExtractor<'a> {
|
|
@@ -711,6 +723,25 @@ impl<'a> CallExtractor<'a> {
|
|
|
711
723
|
out.push(new_call(kind, name, dynamic, opts));
|
|
712
724
|
};
|
|
713
725
|
match s.kind {
|
|
726
|
+
K::CallFunction if word(1) == "CUSTOMER" => {
|
|
727
|
+
// CALL CUSTOMER-FUNCTION 'nnn': the exit EXIT_<main program>_nnn
|
|
728
|
+
let Some(k) = (2..t.len()).find(|k| literal(t[*k]).is_some()) else {
|
|
729
|
+
return t.len();
|
|
730
|
+
};
|
|
731
|
+
let number = literal(t[k]).unwrap_or_default();
|
|
732
|
+
let opts = vec!["CUSTOMER-FUNCTION".to_string()];
|
|
733
|
+
match &self.program {
|
|
734
|
+
Some(p) => push(
|
|
735
|
+
m::CallKind::Function,
|
|
736
|
+
format!("EXIT_{}_{number}", p.to_ascii_uppercase()),
|
|
737
|
+
false,
|
|
738
|
+
opts,
|
|
739
|
+
),
|
|
740
|
+
// the program is not known
|
|
741
|
+
None => push(m::CallKind::Function, text(k, k + 1), true, opts),
|
|
742
|
+
}
|
|
743
|
+
k + 1
|
|
744
|
+
}
|
|
714
745
|
K::CallFunction => {
|
|
715
746
|
let (name, dynamic) = target(2);
|
|
716
747
|
let opts = options(&[
|
|
@@ -1178,6 +1209,7 @@ pub(crate) fn new_call(
|
|
|
1178
1209
|
from: None,
|
|
1179
1210
|
name,
|
|
1180
1211
|
class: None,
|
|
1212
|
+
badi: None,
|
|
1181
1213
|
program: None,
|
|
1182
1214
|
receiver: None,
|
|
1183
1215
|
is_static: false,
|
|
@@ -1379,6 +1411,7 @@ pub fn calls(source: &str) -> Vec<m::Call> {
|
|
|
1379
1411
|
file: &file,
|
|
1380
1412
|
symbols: &symbols,
|
|
1381
1413
|
resolver: &resolver,
|
|
1414
|
+
program: main_program(&symbols),
|
|
1382
1415
|
}
|
|
1383
1416
|
.calls()
|
|
1384
1417
|
}
|
|
@@ -1431,6 +1464,29 @@ CLASS lcl_main IMPLEMENTATION.
|
|
|
1431
1464
|
ENDCLASS.
|
|
1432
1465
|
"#;
|
|
1433
1466
|
|
|
1467
|
+
#[test]
|
|
1468
|
+
fn customer_exits_are_named_by_the_program() {
|
|
1469
|
+
let names = |src: &str| -> Vec<(String, bool)> {
|
|
1470
|
+
calls(src)
|
|
1471
|
+
.into_iter()
|
|
1472
|
+
.map(|c| (c.name, c.dynamic))
|
|
1473
|
+
.collect()
|
|
1474
|
+
};
|
|
1475
|
+
assert_eq!(
|
|
1476
|
+
names("REPORT sapmv45a.\nCALL CUSTOMER-FUNCTION '001' EXPORTING iv = 1."),
|
|
1477
|
+
[("EXIT_SAPMV45A_001".to_string(), false)]
|
|
1478
|
+
);
|
|
1479
|
+
assert_eq!(
|
|
1480
|
+
names("FUNCTION-POOL v45a.\nCALL CUSTOMER-FUNCTION '010'."),
|
|
1481
|
+
[("EXIT_SAPLV45A_010".to_string(), false)]
|
|
1482
|
+
);
|
|
1483
|
+
// an include alone does not tell its program
|
|
1484
|
+
assert_eq!(
|
|
1485
|
+
names("CALL CUSTOMER-FUNCTION '002'."),
|
|
1486
|
+
[("'002'".to_string(), true)]
|
|
1487
|
+
);
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1434
1490
|
#[test]
|
|
1435
1491
|
fn calls_with_types() {
|
|
1436
1492
|
let c = calls(SRC);
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
//! Code of enhancement implementations put where it runs: after an
|
|
2
|
+
//! `ENHANCEMENT-POINT`, in place of an `ENHANCEMENT-SECTION`, or at the begin
|
|
3
|
+
//! or end of a form, function module, method or include.
|
|
4
|
+
//!
|
|
5
|
+
//! The code goes into the source on one line, the line of its position, so
|
|
6
|
+
//! the rows of the rest of the source stay as they are.
|
|
7
|
+
|
|
8
|
+
use abap_source::Hook;
|
|
9
|
+
use abap_syntax::{File, StatementKind as K, Token, TokenKind};
|
|
10
|
+
|
|
11
|
+
/// Where in a source the code of a hook goes.
|
|
12
|
+
struct Edit {
|
|
13
|
+
/// byte offset the code is inserted at
|
|
14
|
+
at: usize,
|
|
15
|
+
/// bytes from `at` on that the code replaces, for sections
|
|
16
|
+
replaces: usize,
|
|
17
|
+
code: String,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// The sources with the hooks put in, for the units that change, and the
|
|
21
|
+
/// hooks that found their position.
|
|
22
|
+
pub(crate) fn apply<'a>(
|
|
23
|
+
units: &[(&str, &File)],
|
|
24
|
+
hooks: &'a [Hook],
|
|
25
|
+
) -> (Vec<(usize, String)>, Vec<&'a Hook>) {
|
|
26
|
+
let mut edits: Vec<Vec<Edit>> = units.iter().map(|_| Vec::new()).collect();
|
|
27
|
+
let mut applied = Vec::new();
|
|
28
|
+
for h in hooks {
|
|
29
|
+
let Some((u, at, replaces)) = position(units, h) else {
|
|
30
|
+
continue;
|
|
31
|
+
};
|
|
32
|
+
edits[u].push(Edit {
|
|
33
|
+
at,
|
|
34
|
+
replaces,
|
|
35
|
+
code: one_line(&h.code),
|
|
36
|
+
});
|
|
37
|
+
applied.push(h);
|
|
38
|
+
}
|
|
39
|
+
let mut out = Vec::new();
|
|
40
|
+
for (u, mut edits) in edits.into_iter().enumerate() {
|
|
41
|
+
if edits.is_empty() {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
let mut text = units[u].1.lexed.source.clone();
|
|
45
|
+
// from the end, so earlier offsets stay valid
|
|
46
|
+
edits.sort_by_key(|e| std::cmp::Reverse(e.at));
|
|
47
|
+
for e in edits {
|
|
48
|
+
let end = e.at + e.replaces;
|
|
49
|
+
// replaced code keeps its line breaks, so rows stay
|
|
50
|
+
let kept: String = text[e.at..end]
|
|
51
|
+
.chars()
|
|
52
|
+
.map(|c| if c == '\n' { '\n' } else { ' ' })
|
|
53
|
+
.collect();
|
|
54
|
+
text.replace_range(e.at..end, &format!(" {} {kept}", e.code));
|
|
55
|
+
}
|
|
56
|
+
out.push((u, text));
|
|
57
|
+
}
|
|
58
|
+
(out, applied)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// The code of a hook on one line, without comments.
|
|
62
|
+
fn one_line(code: &str) -> String {
|
|
63
|
+
let lexed = abap_syntax::lex(code);
|
|
64
|
+
let tokens: Vec<&Token> = lexed
|
|
65
|
+
.tokens
|
|
66
|
+
.iter()
|
|
67
|
+
.filter(|t| t.kind != TokenKind::Comment)
|
|
68
|
+
.collect();
|
|
69
|
+
abap_syntax::join_tokens(tokens)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// The unit, byte offset and length of replaced code for a hook.
|
|
73
|
+
fn position(units: &[(&str, &File)], h: &Hook) -> Option<(usize, usize, usize)> {
|
|
74
|
+
// enhancements of a function module's parameters have two sides
|
|
75
|
+
if h.position.matches("\\SE:").count() > 1 {
|
|
76
|
+
return None;
|
|
77
|
+
}
|
|
78
|
+
let include = h.part("IC");
|
|
79
|
+
let in_unit = |u: usize| include.as_deref().is_none_or(|i| units[u].0 == i);
|
|
80
|
+
if let Some(point) = h.part("EX") {
|
|
81
|
+
for (u, (_, file)) in units.iter().enumerate().filter(|(u, _)| in_unit(*u)) {
|
|
82
|
+
for (i, s) in file.statements.iter().enumerate() {
|
|
83
|
+
// ENHANCEMENT-POINT name SPOTS ...
|
|
84
|
+
let spots = s.tokens.iter().position(|k| file.tokens()[*k].is("SPOTS"));
|
|
85
|
+
let named = spots
|
|
86
|
+
.and_then(|k| k.checked_sub(1))
|
|
87
|
+
.and_then(|k| word(file, s, k))
|
|
88
|
+
.is_some_and(|w| w == point);
|
|
89
|
+
match s.kind {
|
|
90
|
+
K::EnhancementPoint if named => return Some((u, end(file, i), 0)),
|
|
91
|
+
K::EnhancementSection if named => {
|
|
92
|
+
// the implementation replaces the code of the section
|
|
93
|
+
let close = (i + 1..file.statements.len())
|
|
94
|
+
.find(|k| file.statements[*k].kind == K::EndEnhancementSection)?;
|
|
95
|
+
let from = end(file, i);
|
|
96
|
+
return Some((u, from, start(file, close) - from));
|
|
97
|
+
}
|
|
98
|
+
_ => {}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return None;
|
|
103
|
+
}
|
|
104
|
+
let begin = match h.part("SE")?.as_str() {
|
|
105
|
+
"BEGIN" => true,
|
|
106
|
+
"END" => false,
|
|
107
|
+
_ => return None,
|
|
108
|
+
};
|
|
109
|
+
let block = if let Some(form) = h.part("FO") {
|
|
110
|
+
Some((K::Form, K::EndForm, form, None))
|
|
111
|
+
} else if let Some(function) = h.part("FU") {
|
|
112
|
+
Some((K::FunctionModule, K::EndFunction, function, None))
|
|
113
|
+
} else {
|
|
114
|
+
h.part("ME")
|
|
115
|
+
.map(|method| (K::MethodImplementation, K::EndMethod, method, h.part("TY")))
|
|
116
|
+
};
|
|
117
|
+
let Some((open, close, name, class)) = block else {
|
|
118
|
+
// the begin or end of an include
|
|
119
|
+
let include = include?;
|
|
120
|
+
let u = units.iter().position(|(n, _)| *n == include)?;
|
|
121
|
+
let file = units[u].1;
|
|
122
|
+
return Some(if begin {
|
|
123
|
+
(u, 0, 0)
|
|
124
|
+
} else {
|
|
125
|
+
let last = (0..file.statements.len()).rev().find(|i| end(file, *i) > 0);
|
|
126
|
+
(u, last.map_or(0, |i| end(file, i)), 0)
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
for (u, (_, file)) in units.iter().enumerate().filter(|(u, _)| in_unit(*u)) {
|
|
130
|
+
let mut in_class: Option<String> = None;
|
|
131
|
+
for (i, s) in file.statements.iter().enumerate() {
|
|
132
|
+
if s.kind == K::ClassImplementation {
|
|
133
|
+
in_class = word(file, s, 1);
|
|
134
|
+
}
|
|
135
|
+
let found = s.kind == open
|
|
136
|
+
&& word(file, s, 1).is_some_and(|w| w == name)
|
|
137
|
+
&& class
|
|
138
|
+
.as_deref()
|
|
139
|
+
.is_none_or(|c| in_class.as_deref() == Some(c));
|
|
140
|
+
if !found {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if begin {
|
|
144
|
+
return Some((u, end(file, i), 0));
|
|
145
|
+
}
|
|
146
|
+
let k = (i + 1..file.statements.len()).find(|k| file.statements[*k].kind == close)?;
|
|
147
|
+
return Some((u, start(file, k), 0));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
None
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/// The `n`th word of a statement, upper case.
|
|
154
|
+
fn word(file: &File, s: &abap_syntax::Statement, n: usize) -> Option<String> {
|
|
155
|
+
s.tokens.get(n).map(|k| file.tokens()[*k].upper())
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
fn start(file: &File, i: usize) -> usize {
|
|
159
|
+
let s = &file.statements[i];
|
|
160
|
+
s.tokens.first().map_or(0, |k| file.tokens()[*k].span.start)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/// The end of the statement `i`, without comments; 0 for comments only.
|
|
164
|
+
fn end(file: &File, i: usize) -> usize {
|
|
165
|
+
let s = &file.statements[i];
|
|
166
|
+
s.tokens
|
|
167
|
+
.iter()
|
|
168
|
+
.map(|k| &file.tokens()[*k])
|
|
169
|
+
.filter(|t| t.kind != TokenKind::Comment)
|
|
170
|
+
.map(|t| t.span.end)
|
|
171
|
+
.max()
|
|
172
|
+
.unwrap_or(0)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
#[cfg(test)]
|
|
176
|
+
mod tests {
|
|
177
|
+
use super::*;
|
|
178
|
+
|
|
179
|
+
fn hook(position: &str, code: &str) -> Hook {
|
|
180
|
+
Hook {
|
|
181
|
+
enhancement: "ZENH".into(),
|
|
182
|
+
position: position.into(),
|
|
183
|
+
code: code.into(),
|
|
184
|
+
origin: String::new(),
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
fn applied(source: &str, unit: &str, hooks: &[Hook]) -> String {
|
|
189
|
+
let file = abap_syntax::parse(source);
|
|
190
|
+
let (texts, _) = apply(&[(unit, &file)], hooks);
|
|
191
|
+
texts.into_iter().next().map(|(_, t)| t).unwrap_or_default()
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
#[test]
|
|
195
|
+
fn hooks_go_where_they_run() {
|
|
196
|
+
let src = "REPORT zr.
|
|
197
|
+
ENHANCEMENT-POINT ep_top SPOTS zspot STATIC.
|
|
198
|
+
FORM save.
|
|
199
|
+
gv = 1.
|
|
200
|
+
ENDFORM.
|
|
201
|
+
ENHANCEMENT-SECTION es_calc SPOTS zspot.
|
|
202
|
+
gv = 2.
|
|
203
|
+
END-ENHANCEMENT-SECTION.";
|
|
204
|
+
let text = applied(
|
|
205
|
+
src,
|
|
206
|
+
"ZR",
|
|
207
|
+
&[
|
|
208
|
+
hook(
|
|
209
|
+
"\\PR:ZR\\EX:EP_TOP\\EI",
|
|
210
|
+
"ENHANCEMENT 1 ZENH.\n DATA gv_x TYPE i. \" new\nENDENHANCEMENT.",
|
|
211
|
+
),
|
|
212
|
+
hook(
|
|
213
|
+
"\\PR:ZR\\FO:SAVE\\SE:END\\EI",
|
|
214
|
+
"ENHANCEMENT 2 ZENH.\n gv = 3.\nENDENHANCEMENT.",
|
|
215
|
+
),
|
|
216
|
+
hook(
|
|
217
|
+
"\\PR:ZR\\FO:SAVE\\SE:BEGIN\\EI",
|
|
218
|
+
"ENHANCEMENT 3 ZENH.\n gv = 4.\nENDENHANCEMENT.",
|
|
219
|
+
),
|
|
220
|
+
hook(
|
|
221
|
+
"\\PR:ZR\\EX:ES_CALC\\EI",
|
|
222
|
+
"ENHANCEMENT 4 ZENH.\n gv = 5.\nENDENHANCEMENT.",
|
|
223
|
+
),
|
|
224
|
+
hook(
|
|
225
|
+
"\\PR:ZR\\FO:NONE\\SE:END\\EI",
|
|
226
|
+
"ENHANCEMENT 5 ZENH.\nENDENHANCEMENT.",
|
|
227
|
+
),
|
|
228
|
+
],
|
|
229
|
+
);
|
|
230
|
+
let lines: Vec<&str> = text.lines().map(str::trim_end).collect();
|
|
231
|
+
assert_eq!(
|
|
232
|
+
lines,
|
|
233
|
+
[
|
|
234
|
+
"REPORT zr.",
|
|
235
|
+
"ENHANCEMENT-POINT ep_top SPOTS zspot STATIC. ENHANCEMENT 1 ZENH. DATA gv_x TYPE i. ENDENHANCEMENT.",
|
|
236
|
+
"FORM save. ENHANCEMENT 3 ZENH. gv = 4. ENDENHANCEMENT.",
|
|
237
|
+
" gv = 1.",
|
|
238
|
+
" ENHANCEMENT 2 ZENH. gv = 3. ENDENHANCEMENT. ENDFORM.",
|
|
239
|
+
"ENHANCEMENT-SECTION es_calc SPOTS zspot. ENHANCEMENT 4 ZENH. gv = 5. ENDENHANCEMENT.",
|
|
240
|
+
"",
|
|
241
|
+
"END-ENHANCEMENT-SECTION.",
|
|
242
|
+
]
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
#[test]
|
|
247
|
+
fn methods_function_modules_and_includes() {
|
|
248
|
+
let src = "CLASS lcl IMPLEMENTATION.
|
|
249
|
+
METHOD run.
|
|
250
|
+
ENDMETHOD.
|
|
251
|
+
ENDCLASS.
|
|
252
|
+
CLASS zcl_x IMPLEMENTATION.
|
|
253
|
+
METHOD run.
|
|
254
|
+
ENDMETHOD.
|
|
255
|
+
ENDCLASS.";
|
|
256
|
+
let text = applied(
|
|
257
|
+
src,
|
|
258
|
+
"ZCL_X=========================CP",
|
|
259
|
+
&[hook(
|
|
260
|
+
"\\TY:ZCL_X\\ME:RUN\\SE:BEGIN\\EI",
|
|
261
|
+
"ENHANCEMENT 1 ZENH.\nx = 1.\nENDENHANCEMENT.",
|
|
262
|
+
)],
|
|
263
|
+
);
|
|
264
|
+
assert_eq!(
|
|
265
|
+
text.lines().nth(5).unwrap(),
|
|
266
|
+
" METHOD run. ENHANCEMENT 1 ZENH. x = 1. ENDENHANCEMENT. "
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
let text = applied(
|
|
270
|
+
"FUNCTION z_fm.\nENDFUNCTION.",
|
|
271
|
+
"Z_FM",
|
|
272
|
+
&[hook(
|
|
273
|
+
"\\FU:Z_FM\\SE:BEGIN\\EI",
|
|
274
|
+
"ENHANCEMENT 1 ZENH.\nx = 1.\nENDENHANCEMENT.",
|
|
275
|
+
)],
|
|
276
|
+
);
|
|
277
|
+
assert_eq!(
|
|
278
|
+
text.lines().next().unwrap(),
|
|
279
|
+
"FUNCTION z_fm. ENHANCEMENT 1 ZENH. x = 1. ENDENHANCEMENT. "
|
|
280
|
+
);
|
|
281
|
+
// parameters of function modules are not code
|
|
282
|
+
let text = applied(
|
|
283
|
+
"FUNCTION z_fm.\nENDFUNCTION.",
|
|
284
|
+
"Z_FM",
|
|
285
|
+
&[hook(
|
|
286
|
+
"\\FU:Z_FM\\SE:IMPORTING\\SE:END\\EI",
|
|
287
|
+
"ENHANCEMENT 1 ZENH.\nENDENHANCEMENT.",
|
|
288
|
+
)],
|
|
289
|
+
);
|
|
290
|
+
assert_eq!(text, "");
|
|
291
|
+
|
|
292
|
+
let text = applied(
|
|
293
|
+
"x = 1.\n\" end",
|
|
294
|
+
"ZINC",
|
|
295
|
+
&[hook(
|
|
296
|
+
"\\PR:ZR\\IC:ZINC\\SE:END\\EI",
|
|
297
|
+
"ENHANCEMENT 1 ZENH.\ny = 2.\nENDENHANCEMENT.",
|
|
298
|
+
)],
|
|
299
|
+
);
|
|
300
|
+
assert_eq!(
|
|
301
|
+
text,
|
|
302
|
+
"x = 1. ENHANCEMENT 1 ZENH. y = 2. ENDENHANCEMENT. \n\" end"
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
@@ -40,6 +40,7 @@ pub fn logic_of(file: &File) -> m::FileLogic {
|
|
|
40
40
|
file,
|
|
41
41
|
symbols: &symbols,
|
|
42
42
|
resolver: &resolver,
|
|
43
|
+
program: crate::calls::main_program(&symbols),
|
|
43
44
|
}
|
|
44
45
|
.calls();
|
|
45
46
|
let attributes = |class: &str| class_attributes(&symbols, None, class);
|
|
@@ -73,6 +74,8 @@ impl Workspace {
|
|
|
73
74
|
/// in the TOP include, has the scope `global`. Calls into other objects
|
|
74
75
|
/// are followed up to `depth` hops, calls within the object always.
|
|
75
76
|
pub fn logic(&self, root: &SourceRef, depth: u32) -> m::FileLogic {
|
|
77
|
+
// the engine loads callees one by one: load them before, several at once
|
|
78
|
+
self.preload(std::slice::from_ref(root), depth);
|
|
76
79
|
let mut engine = Engine::new(Some(self), depth);
|
|
77
80
|
let procedures = match engine.load(root, 0) {
|
|
78
81
|
Some(o) => engine.finish(o),
|
|
@@ -92,7 +95,7 @@ impl Workspace {
|
|
|
92
95
|
|
|
93
96
|
/// Calls through an interface are followed into at most this many
|
|
94
97
|
/// implementations; with more, which one runs says too little.
|
|
95
|
-
const MAX_IMPLEMENTATIONS: usize = 10;
|
|
98
|
+
pub(crate) const MAX_IMPLEMENTATIONS: usize = 10;
|
|
96
99
|
|
|
97
100
|
/// Subclasses looked at for redefinitions of a called method; with more,
|
|
98
101
|
/// a class is too general to tell which redefinition runs.
|
|
@@ -463,7 +466,14 @@ impl<'w> Engine<'w> {
|
|
|
463
466
|
if !interface || name.contains('~') {
|
|
464
467
|
return Vec::new();
|
|
465
468
|
}
|
|
466
|
-
|
|
469
|
+
// CALL BADI runs the active implementations of the BAdI
|
|
470
|
+
let classes = match (&c.badi, self.workspace) {
|
|
471
|
+
(Some(badi), Some(ws)) => ws
|
|
472
|
+
.badi(badi)
|
|
473
|
+
.map(|b| crate::workspace::badi_classes(&b))
|
|
474
|
+
.unwrap_or_default(),
|
|
475
|
+
_ => self.implementations(o, &class),
|
|
476
|
+
};
|
|
467
477
|
if classes.len() > MAX_IMPLEMENTATIONS {
|
|
468
478
|
return Vec::new();
|
|
469
479
|
}
|
|
@@ -523,14 +523,23 @@ fn call(c: &m::Call) -> String {
|
|
|
523
523
|
if !c.resolved {
|
|
524
524
|
flags.push("unresolved");
|
|
525
525
|
}
|
|
526
|
+
// CALL BADI with the BAdI, its interface is the class
|
|
527
|
+
let options: Vec<String> = c
|
|
528
|
+
.options
|
|
529
|
+
.iter()
|
|
530
|
+
.map(|o| match &c.badi {
|
|
531
|
+
Some(b) if o == "BADI" => format!("BADI {b}"),
|
|
532
|
+
_ => o.clone(),
|
|
533
|
+
})
|
|
534
|
+
.collect();
|
|
526
535
|
format!(
|
|
527
536
|
" {:>5} {from}: {} {target}{}{}\n",
|
|
528
537
|
c.span.start.row,
|
|
529
538
|
format!("{:?}", c.kind).to_ascii_lowercase(),
|
|
530
|
-
if
|
|
539
|
+
if options.is_empty() {
|
|
531
540
|
String::new()
|
|
532
541
|
} else {
|
|
533
|
-
format!(" [{}]",
|
|
542
|
+
format!(" [{}]", options.join(", "))
|
|
534
543
|
},
|
|
535
544
|
if flags.is_empty() {
|
|
536
545
|
String::new()
|