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.
@@ -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
- Open3.popen2(bin_path, '-s') do |input, output, _thread|
14
- input.puts analysis.to_json
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'
@@ -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 ast
9
- @language.ast @content
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.ast(ast)
51
- new Mulang::Language::External.new, ast
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']
@@ -1,12 +1,63 @@
1
1
  module Mulang::Expectation
2
- SMELLS = %w(DiscardsExceptions DoesConsolePrint DoesNilTest DoesNullTest DoesTypeTest
3
- HasAssignmentReturn HasCodeDuplication HasEmptyIfBranches HasRedundantRepeat HasLongParameterList
4
- HasMisspelledBindings HasMisspelledIdentifiers
5
- HasRedundantBooleanComparison HasRedundantGuards HasRedundantIf
6
- HasRedundantLambda HasRedundantLocalVariableReturn HasRedundantParameter
7
- HasRedundantReduction HasTooManyMethods HasTooShortBindings HasTooShortIdentifiers HasUnreachableCode
8
- HasWrongCaseBinding HasWrongCaseIdentifiers IsLongCode OverridesEqualOrHashButNotBoth
9
- ReturnsNil ReturnsNull UsesCut UsesFail UsesUnificationOperator)
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)
@@ -9,6 +9,8 @@ class Mulang::Expectation::Custom
9
9
  name
10
10
  end
11
11
 
12
+ alias translate! translate
13
+
12
14
  def to_h
13
15
  {binding: '<<custom>>', inspection: name}
14
16
  end
@@ -1,49 +1,35 @@
1
1
  module Mulang::Expectation::I18n
2
2
  class << self
3
- DEFAULT_KEYWORDS = {
4
- keyword_fail: :fail,
5
- keyword_false: :false,
6
- keyword_findall: :findall,
7
- keyword_for: :for,
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, keywords = nil)
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, keywords)
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, keywords)
34
- with_keywords keywords,
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(keywords, e.inspection)
24
+ matching: t_matching(tokens, e.inspection)
39
25
  end
40
26
 
41
27
  def key_for(binding, inspection)
42
- "mulang.inspection.#{inspection.type}#{inspection.target ? inspection.target.i18n_suffix : nil}"
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") : "<strong>#{Mulang::Inspection.parse_binding_name binding}</strong>"
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
- "<strong>#{parsed.target.value}</strong>" if parsed.target
40
+ "<code>#{parsed.target.value}</code>" if parsed.target
55
41
  end
56
42
 
57
- def t_matching(keywords, parsed)
58
- ::I18n.t("mulang.expectation.#{parsed.matcher.type}", with_keywords(keywords, value: parsed.matcher.value)) if parsed.matcher
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 with_keywords(keywords, params)
62
- params.merge(DEFAULT_KEYWORDS).merge(keywords || {})
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(keywords = nil)
15
- Mulang::Expectation::I18n.translate self, keywords
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('Has', 'Uses'), nil)
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)
@@ -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
 
@@ -1,19 +1,52 @@
1
1
  module Mulang::Language
2
- class Native
3
- def initialize(language)
4
- @language = language
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 ast(content)
8
- Mulang.analyse(ast_analysis(content))['intermediateLanguage'] rescue nil
7
+ def identifiers_analysis(content, **options)
8
+ base_analysis content, {includeOutputIdentifiers: true}, **options
9
9
  end
10
10
 
11
- def ast_analysis(content)
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: { tag: 'CodeSample', language: @language, content: content },
14
- spec: { expectations: [], smellsSet: { tag: 'NoSmells' }, includeIntermediateLanguage: true }
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