mulang 5.1.0 → 6.0.0
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.
- checksums.yaml +4 -4
- data/bin/console +2 -2
- data/bin/mulang +0 -0
- data/lib/locales/en.yml +41 -42
- data/lib/locales/es.yml +40 -41
- data/lib/locales/operators.yml +96 -0
- data/lib/locales/pt.yml +39 -40
- data/lib/mulang.rb +53 -4
- data/lib/mulang/code.rb +36 -6
- data/lib/mulang/expectation.rb +60 -9
- data/lib/mulang/expectation/custom.rb +2 -0
- data/lib/mulang/expectation/i18n.rb +25 -31
- data/lib/mulang/expectation/standard.rb +7 -3
- data/lib/mulang/inspection.rb +27 -2
- data/lib/mulang/language.rb +43 -10
- data/lib/mulang/sexp.rb +80 -0
- data/lib/mulang/tokens.rb +275 -0
- data/lib/mulang/version.rb +2 -2
- metadata +22 -6
data/lib/mulang.rb
CHANGED
@@ -9,16 +9,65 @@ module Mulang
|
|
9
9
|
def self.bin_path
|
10
10
|
File.join(__dir__, '..', 'bin', 'mulang')
|
11
11
|
end
|
12
|
-
def self.analyse(analysis)
|
13
|
-
|
14
|
-
|
12
|
+
def self.analyse(analysis, **options)
|
13
|
+
arg, mode = Mulang::RunMode.for analysis, options
|
14
|
+
Open3.popen2(bin_path, arg) do |input, output, _thread|
|
15
|
+
input.puts mode.input(analysis).to_json
|
15
16
|
input.close
|
16
|
-
JSON.parse output.read
|
17
|
+
result = JSON.parse output.read
|
18
|
+
mode.output result
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
module RunMode
|
25
|
+
def self.for(analysis, options)
|
26
|
+
serialization = options[:serialization]
|
27
|
+
many = analysis.is_a?(Array)
|
28
|
+
if many
|
29
|
+
[encode_serialization(serialization), Mulang::RunMode::Natural]
|
30
|
+
elsif serialization
|
31
|
+
[encode_serialization(serialization), Mulang::RunMode::ForcedMany]
|
32
|
+
else
|
33
|
+
["-s", Mulang::RunMode::Natural]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.encode_serialization(option)
|
38
|
+
case option
|
39
|
+
when nil then '-S'
|
40
|
+
when :bracket then '-B'
|
41
|
+
when :brace then '-C'
|
42
|
+
else raise "Unsupported serialization #{option}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module Natural
|
47
|
+
def self.input(analysis)
|
48
|
+
analysis
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.output(result)
|
52
|
+
result
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
module ForcedMany
|
57
|
+
def self.input(analysis)
|
58
|
+
[analysis]
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.output(result)
|
62
|
+
result[0]
|
63
|
+
end
|
17
64
|
end
|
18
65
|
end
|
19
66
|
end
|
20
67
|
|
68
|
+
require_relative './mulang/tokens'
|
21
69
|
require_relative './mulang/inspection'
|
22
70
|
require_relative './mulang/expectation'
|
23
71
|
require_relative './mulang/code'
|
24
72
|
require_relative './mulang/language'
|
73
|
+
require_relative './mulang/sexp'
|
data/lib/mulang/code.rb
CHANGED
@@ -1,12 +1,29 @@
|
|
1
1
|
module Mulang
|
2
2
|
class Code
|
3
|
+
attr_accessor :language, :content
|
3
4
|
def initialize(language, content)
|
4
5
|
@language = language
|
5
6
|
@content = content
|
6
7
|
end
|
7
8
|
|
8
|
-
def
|
9
|
-
@language.
|
9
|
+
def identifiers(**options)
|
10
|
+
@language.identifiers @content, **options
|
11
|
+
end
|
12
|
+
|
13
|
+
def ast(**options)
|
14
|
+
@language.ast @content, **options
|
15
|
+
end
|
16
|
+
|
17
|
+
def ast_analysis(**options)
|
18
|
+
@language.ast_analysis @content, **options
|
19
|
+
end
|
20
|
+
|
21
|
+
def transformed_asts(operations, **options)
|
22
|
+
@language.transformed_asts @content, operations, **options
|
23
|
+
end
|
24
|
+
|
25
|
+
def transformed_asts_analysis(operations, **options)
|
26
|
+
@language.transformed_asts_analysis @content, operations, **options
|
10
27
|
end
|
11
28
|
|
12
29
|
def sample
|
@@ -17,8 +34,8 @@ module Mulang
|
|
17
34
|
{ sample: sample, spec: spec }
|
18
35
|
end
|
19
36
|
|
20
|
-
def analyse(spec)
|
21
|
-
Mulang.analyse analysis(spec)
|
37
|
+
def analyse(spec, **options)
|
38
|
+
Mulang.analyse analysis(spec), **options
|
22
39
|
end
|
23
40
|
|
24
41
|
def expect(binding='*', inspection)
|
@@ -47,12 +64,25 @@ module Mulang
|
|
47
64
|
new Mulang::Language::External.new(&tool), content
|
48
65
|
end
|
49
66
|
|
50
|
-
def self.
|
51
|
-
|
67
|
+
def self.analyse_many(codes, spec, **options)
|
68
|
+
run_many(codes, **options) { |it| it.analysis(spec) }
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.ast_many(codes, **options)
|
72
|
+
run_many(codes, key: 'outputAst', **options) { |it| it.ast_analysis(**options) }
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.transformed_asts_many(codes, operations, **options)
|
76
|
+
run_many(codes, key: 'transformedAsts', **options) { |it| it.transformed_asts_analysis(operations, **options) }
|
52
77
|
end
|
53
78
|
|
54
79
|
private
|
55
80
|
|
81
|
+
def self.run_many(codes, key: nil, **options)
|
82
|
+
result = Mulang.analyse(codes.map { |it| yield it }, **options)
|
83
|
+
key ? result.map { |it| it[key] } : result
|
84
|
+
end
|
85
|
+
|
56
86
|
def expectation_results_for(result)
|
57
87
|
raise result['reason'] if result['tag'] == 'AnalysisFailed'
|
58
88
|
result['expectationResults']
|
data/lib/mulang/expectation.rb
CHANGED
@@ -1,12 +1,63 @@
|
|
1
1
|
module Mulang::Expectation
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
2
|
+
LOGIC_SMELLS = %w(
|
3
|
+
HasRedundantReduction
|
4
|
+
UsesCut
|
5
|
+
UsesFail
|
6
|
+
UsesUnificationOperator
|
7
|
+
)
|
8
|
+
|
9
|
+
FUNCTIONAL_SMELLS = %w(
|
10
|
+
HasRedundantGuards
|
11
|
+
HasRedundantParameter
|
12
|
+
ShouldUseOtherwise
|
13
|
+
)
|
14
|
+
|
15
|
+
OBJECT_ORIENTED_SMELLS = %w(
|
16
|
+
DoesNilTest
|
17
|
+
DoesNullTest
|
18
|
+
HasTooManyMethods
|
19
|
+
OverridesEqualOrHashButNotBoth
|
20
|
+
ReturnsNil
|
21
|
+
ReturnsNull
|
22
|
+
)
|
23
|
+
|
24
|
+
IMPERATIVE_SMELLS = %w(
|
25
|
+
HasAssignmentCondition
|
26
|
+
HasAssignmentReturn
|
27
|
+
HasEmptyRepeat
|
28
|
+
HasRedundantLocalVariableReturn
|
29
|
+
HasRedundantRepeat
|
30
|
+
)
|
31
|
+
|
32
|
+
EXPRESSIVENESS_SMELLS = %w(
|
33
|
+
HasMisspelledBindings
|
34
|
+
HasMisspelledIdentifiers
|
35
|
+
HasTooShortIdentifiers
|
36
|
+
HasWrongCaseBinding
|
37
|
+
HasWrongCaseIdentifiers
|
38
|
+
)
|
39
|
+
|
40
|
+
GENERIC_SMELLS = %w(
|
41
|
+
DiscardsExceptions
|
42
|
+
DoesConsolePrint
|
43
|
+
DoesTypeTest
|
44
|
+
HasCodeDuplication
|
45
|
+
HasDeclarationTypos
|
46
|
+
HasEmptyIfBranches
|
47
|
+
HasEqualIfBranches
|
48
|
+
HasLongParameterList
|
49
|
+
HasRedundantBooleanComparison
|
50
|
+
HasRedundantIf
|
51
|
+
HasRedundantLambda
|
52
|
+
HasTooShortBindings
|
53
|
+
HasUnreachableCode
|
54
|
+
HasUsageTypos
|
55
|
+
IsLongCode
|
56
|
+
ShouldInvertIfCondition
|
57
|
+
ShouldUseStrictComparators
|
58
|
+
)
|
59
|
+
|
60
|
+
SMELLS = GENERIC_SMELLS + EXPRESSIVENESS_SMELLS + IMPERATIVE_SMELLS + OBJECT_ORIENTED_SMELLS + FUNCTIONAL_SMELLS + LOGIC_SMELLS
|
10
61
|
|
11
62
|
def self.guess_type(expectation)
|
12
63
|
if expectation[:binding] == '<<custom>>'
|
@@ -19,7 +70,7 @@ module Mulang::Expectation
|
|
19
70
|
end
|
20
71
|
|
21
72
|
def self.has_smell?(smell)
|
22
|
-
SMELLS.include? smell
|
73
|
+
SMELLS.include? smell.split(':').first
|
23
74
|
end
|
24
75
|
|
25
76
|
def self.parse(expectation)
|
@@ -1,49 +1,35 @@
|
|
1
1
|
module Mulang::Expectation::I18n
|
2
2
|
class << self
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
keyword_forall: :forall,
|
9
|
-
keyword_foreach: :foreach,
|
10
|
-
keyword_if: :if,
|
11
|
-
keyword_is: :is,
|
12
|
-
keyword_not: :not,
|
13
|
-
keyword_null: :null,
|
14
|
-
keyword_repeat: :repeat,
|
15
|
-
keyword_switch: :switch,
|
16
|
-
keyword_true: :true,
|
17
|
-
keyword_while: :while,
|
18
|
-
keyword_yield: :yield
|
19
|
-
}
|
3
|
+
def translate(e, tokens = nil)
|
4
|
+
translate!(e, tokens)
|
5
|
+
rescue
|
6
|
+
'<unknown expectation>'
|
7
|
+
end
|
20
8
|
|
21
|
-
def translate(e,
|
9
|
+
def translate!(e, tokens = nil)
|
22
10
|
e = e.as_v2
|
23
11
|
key = key_for e.binding, e.inspection
|
24
|
-
::I18n.t key, translation_params(e,
|
25
|
-
rescue
|
26
|
-
'<unknown expectation>'
|
12
|
+
::I18n.t key, translation_params(e, tokens)
|
27
13
|
end
|
28
14
|
|
29
15
|
alias t translate
|
30
16
|
|
31
17
|
private
|
32
18
|
|
33
|
-
def translation_params(e,
|
34
|
-
|
19
|
+
def translation_params(e, tokens)
|
20
|
+
with_tokens tokens,
|
35
21
|
binding: t_binding(e.binding),
|
36
22
|
target: t_target(e.inspection),
|
37
23
|
must: t_must(e.inspection),
|
38
|
-
matching: t_matching(
|
24
|
+
matching: t_matching(tokens, e.inspection)
|
39
25
|
end
|
40
26
|
|
41
27
|
def key_for(binding, inspection)
|
42
|
-
"
|
28
|
+
"#{inspection.i18n_namespace}.#{inspection.type}#{inspection.target ? inspection.target.i18n_suffix : nil}"
|
43
29
|
end
|
44
30
|
|
45
31
|
def t_binding(binding)
|
46
|
-
binding == '*' ? ::I18n.t("mulang.expectation.solution") : "<
|
32
|
+
binding == '*' ? ::I18n.t("mulang.expectation.solution") : "<code>#{Mulang::Inspection.parse_binding_name binding}</code>"
|
47
33
|
end
|
48
34
|
|
49
35
|
def t_must(parsed)
|
@@ -51,15 +37,23 @@ module Mulang::Expectation::I18n
|
|
51
37
|
end
|
52
38
|
|
53
39
|
def t_target(parsed)
|
54
|
-
"<
|
40
|
+
"<code>#{parsed.target.value}</code>" if parsed.target
|
55
41
|
end
|
56
42
|
|
57
|
-
def t_matching(
|
58
|
-
::I18n.t("mulang.expectation.#{parsed.matcher.type}",
|
43
|
+
def t_matching(tokens, parsed)
|
44
|
+
::I18n.t("mulang.expectation.#{parsed.matcher.type}", with_tokens(tokens, value: parsed.matcher.value)) if parsed.matcher
|
59
45
|
end
|
60
46
|
|
61
|
-
def
|
62
|
-
|
47
|
+
def with_tokens(tokens, params)
|
48
|
+
hash = if tokens.nil?
|
49
|
+
{}
|
50
|
+
elsif tokens.is_a?(Hash)
|
51
|
+
tokens
|
52
|
+
else
|
53
|
+
params.merge(Mulang::Tokens::TOKENS.indifferent_get(tokens))
|
54
|
+
end
|
55
|
+
|
56
|
+
params.merge(Mulang::Tokens::DEFAULT_TOKENS.merge(hash))
|
63
57
|
end
|
64
58
|
end
|
65
59
|
end
|
@@ -11,8 +11,12 @@ class Mulang::Expectation::Standard
|
|
11
11
|
raise "Wrong inspection #{to_h}" unless inspection?
|
12
12
|
end
|
13
13
|
|
14
|
-
def translate(
|
15
|
-
Mulang::Expectation::I18n.translate self,
|
14
|
+
def translate(tokens = nil)
|
15
|
+
Mulang::Expectation::I18n.translate self, tokens
|
16
|
+
end
|
17
|
+
|
18
|
+
def translate!(tokens = nil)
|
19
|
+
Mulang::Expectation::I18n.translate! self, tokens
|
16
20
|
end
|
17
21
|
|
18
22
|
def to_h
|
@@ -68,7 +72,7 @@ class Mulang::Expectation::V0 < Mulang::Expectation::Standard
|
|
68
72
|
end
|
69
73
|
|
70
74
|
def as_v2_use
|
71
|
-
Mulang::Expectation::V2.new binding, new_inspection(inspection.type.gsub(
|
75
|
+
Mulang::Expectation::V2.new binding, new_inspection(inspection.type.gsub(/^Has/, 'Uses'), nil)
|
72
76
|
end
|
73
77
|
|
74
78
|
def as_v2_declare(simple_type)
|
data/lib/mulang/inspection.rb
CHANGED
@@ -20,11 +20,12 @@ module Mulang
|
|
20
20
|
attr_accessor :type, :target, :matcher, :negated
|
21
21
|
alias negated? negated
|
22
22
|
|
23
|
-
def initialize(type, target, negated: false, matcher: nil)
|
23
|
+
def initialize(type, target, negated: false, matcher: nil, i18n_namespace: nil)
|
24
24
|
@type = type
|
25
25
|
@target = target
|
26
26
|
@negated = negated
|
27
27
|
@matcher = matcher
|
28
|
+
@i18n_namespace = i18n_namespace
|
28
29
|
end
|
29
30
|
|
30
31
|
def to_s
|
@@ -43,6 +44,10 @@ module Mulang
|
|
43
44
|
matcher ? ":#{matcher.to_s}" : nil
|
44
45
|
end
|
45
46
|
|
47
|
+
def i18n_namespace
|
48
|
+
@i18n_namespace || 'mulang.inspection'
|
49
|
+
end
|
50
|
+
|
46
51
|
def self.parse_binding_name(binding_s)
|
47
52
|
if binding_s.start_with? 'Intransitive:'
|
48
53
|
binding_s[13..-1]
|
@@ -52,14 +57,34 @@ module Mulang
|
|
52
57
|
end
|
53
58
|
|
54
59
|
def self.parse(inspection_s)
|
60
|
+
parse_extension(inspection_s).try { |it| return it }
|
55
61
|
match = REGEXP.match inspection_s
|
56
62
|
raise "Invalid inspection #{inspection_s}" unless match
|
57
|
-
Inspection.new(
|
63
|
+
Mulang::Inspection.new(
|
58
64
|
match['type'],
|
59
65
|
Mulang::Inspection::Target.parse(match['target']),
|
60
66
|
matcher: Mulang::Inspection::Matcher.parse(match['matcher'], match['value']),
|
61
67
|
negated: match['negation'].present?)
|
62
68
|
end
|
69
|
+
|
70
|
+
def self.parse_extension(inspection_s)
|
71
|
+
extensions.each do |extension|
|
72
|
+
extension.parse(inspection_s).try { |it| return it }
|
73
|
+
end
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.extensions
|
78
|
+
@extensions ||= []
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.register_extension!(extension)
|
82
|
+
extensions << extension
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.unregister_extension!(extension)
|
86
|
+
extensions.delete extension
|
87
|
+
end
|
63
88
|
end
|
64
89
|
end
|
65
90
|
|
data/lib/mulang/language.rb
CHANGED
@@ -1,19 +1,52 @@
|
|
1
1
|
module Mulang::Language
|
2
|
-
class
|
3
|
-
def
|
4
|
-
|
2
|
+
class Base
|
3
|
+
def identifiers(content, **options)
|
4
|
+
Mulang.analyse(identifiers_analysis(content, **options), **options)['outputIdentifiers'] rescue nil
|
5
5
|
end
|
6
6
|
|
7
|
-
def
|
8
|
-
|
7
|
+
def identifiers_analysis(content, **options)
|
8
|
+
base_analysis content, {includeOutputIdentifiers: true}, **options
|
9
9
|
end
|
10
10
|
|
11
|
-
def
|
11
|
+
def transformed_asts(content, operations, **options)
|
12
|
+
Mulang.analyse(transformed_asts_analysis(content, operations, **options), **options)['transformedAsts'] rescue nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def transformed_asts_analysis(content, operations, **options)
|
16
|
+
base_analysis content, {transformationSpecs: operations}, **options
|
17
|
+
end
|
18
|
+
|
19
|
+
def normalization_options(**options)
|
20
|
+
options.except(:serialization).presence
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def base_analysis(content, spec, **options)
|
12
26
|
{
|
13
|
-
sample:
|
14
|
-
spec: {
|
27
|
+
sample: sample(content),
|
28
|
+
spec: {
|
29
|
+
expectations: [],
|
30
|
+
smellsSet: { tag: 'NoSmells' },
|
31
|
+
includeOutputAst: false,
|
32
|
+
normalizationOptions: normalization_options(options)
|
33
|
+
}.merge(spec).compact
|
15
34
|
}
|
16
35
|
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Native < Base
|
39
|
+
def initialize(language)
|
40
|
+
@language = language
|
41
|
+
end
|
42
|
+
|
43
|
+
def ast(content, **options)
|
44
|
+
Mulang.analyse(ast_analysis(content, **options), **options)['outputAst'] rescue nil
|
45
|
+
end
|
46
|
+
|
47
|
+
def ast_analysis(content, **options)
|
48
|
+
base_analysis content, {includeOutputAst: true}, **options
|
49
|
+
end
|
17
50
|
|
18
51
|
def sample(content)
|
19
52
|
{
|
@@ -24,12 +57,12 @@ module Mulang::Language
|
|
24
57
|
end
|
25
58
|
end
|
26
59
|
|
27
|
-
class External
|
60
|
+
class External < Base
|
28
61
|
def initialize(&tool)
|
29
62
|
@tool = block_given? ? tool : proc { |it| it }
|
30
63
|
end
|
31
64
|
|
32
|
-
def ast(content)
|
65
|
+
def ast(content, **args)
|
33
66
|
@tool.call(content) rescue nil
|
34
67
|
end
|
35
68
|
|