mulang 5.1.0.2 → 6.0.2

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)
@@ -43,12 +60,25 @@ module Mulang
43
60
  native(*args).tap { |it| it.expect('Parses') }
44
61
  end
45
62
 
46
- def self.external(content, &tool)
47
- new Mulang::Language::External.new(&tool), content
63
+ def self.external(language_name = nil, content, &tool)
64
+ new Mulang::Language::External.new(language_name, &tool), content
65
+ end
66
+
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) }
48
77
  end
49
78
 
50
- def self.ast(ast)
51
- new Mulang::Language::External.new, ast
79
+ def self.run_many(codes, key: nil, **options)
80
+ result = Mulang.analyse(codes.map { |it| yield it }, **options)
81
+ key ? result.map { |it| it[key] } : result
52
82
  end
53
83
 
54
84
  private
@@ -1,12 +1,73 @@
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
+ DoesTypeTest
23
+ )
24
+
25
+ IMPERATIVE_SMELLS = %w(
26
+ HasAssignmentCondition
27
+ HasAssignmentReturn
28
+ HasEmptyRepeat
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
+ HasCodeDuplication
44
+ HasDeclarationTypos
45
+ HasEmptyIfBranches
46
+ HasEqualIfBranches
47
+ HasLongParameterList
48
+ HasRedundantBooleanComparison
49
+ HasRedundantLocalVariableReturn
50
+ HasRedundantIf
51
+ HasRedundantLambda
52
+ HasTooShortBindings
53
+ HasUnreachableCode
54
+ HasUsageTypos
55
+ IsLongCode
56
+ ShouldInvertIfCondition
57
+ ShouldUseStrictComparators
58
+ )
59
+
60
+ JAVA_SCRIPT_SMELLS = %w(
61
+ JavaScript#UsesVarInsteadOfLet
62
+ )
63
+
64
+ SMELLS = GENERIC_SMELLS +
65
+ EXPRESSIVENESS_SMELLS +
66
+ IMPERATIVE_SMELLS +
67
+ OBJECT_ORIENTED_SMELLS +
68
+ FUNCTIONAL_SMELLS +
69
+ LOGIC_SMELLS +
70
+ JAVA_SCRIPT_SMELLS
10
71
 
11
72
  def self.guess_type(expectation)
12
73
  if expectation[:binding] == '<<custom>>'
@@ -19,7 +80,7 @@ module Mulang::Expectation
19
80
  end
20
81
 
21
82
  def self.has_smell?(smell)
22
- SMELLS.include? smell
83
+ SMELLS.include? smell.split(':').first
23
84
  end
24
85
 
25
86
  def self.parse(expectation)
@@ -1,45 +1,27 @@
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
- }
20
-
21
- def translate(e, keywords = nil)
22
- translate!(e, keywords)
3
+ def translate(e, tokens = nil)
4
+ translate!(e, tokens)
23
5
  rescue
24
6
  '<unknown expectation>'
25
7
  end
26
8
 
27
- def translate!(e, keywords = nil)
9
+ def translate!(e, tokens = nil)
28
10
  e = e.as_v2
29
11
  key = key_for e.binding, e.inspection
30
- ::I18n.t key, translation_params(e, keywords)
12
+ ::I18n.t key, translation_params(e, tokens)
31
13
  end
32
14
 
33
15
  alias t translate
34
16
 
35
17
  private
36
18
 
37
- def translation_params(e, keywords)
38
- with_keywords keywords,
19
+ def translation_params(e, tokens)
20
+ with_tokens tokens,
39
21
  binding: t_binding(e.binding),
40
22
  target: t_target(e.inspection),
41
23
  must: t_must(e.inspection),
42
- matching: t_matching(keywords, e.inspection)
24
+ matching: t_matching(tokens, e.inspection)
43
25
  end
44
26
 
45
27
  def key_for(binding, inspection)
@@ -47,7 +29,7 @@ module Mulang::Expectation::I18n
47
29
  end
48
30
 
49
31
  def t_binding(binding)
50
- 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>"
51
33
  end
52
34
 
53
35
  def t_must(parsed)
@@ -55,15 +37,23 @@ module Mulang::Expectation::I18n
55
37
  end
56
38
 
57
39
  def t_target(parsed)
58
- "<strong>#{parsed.target.value}</strong>" if parsed.target
40
+ "<code>#{parsed.target.value}</code>" if parsed.target
59
41
  end
60
42
 
61
- def t_matching(keywords, parsed)
62
- ::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
63
45
  end
64
46
 
65
- def with_keywords(keywords, params)
66
- 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))
67
57
  end
68
58
  end
69
59
  end
@@ -11,12 +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
16
  end
17
17
 
18
- def translate!(keywords = nil)
19
- Mulang::Expectation::I18n.translate! self, keywords
18
+ def translate!(tokens = nil)
19
+ Mulang::Expectation::I18n.translate! self, tokens
20
20
  end
21
21
 
22
22
  def to_h
@@ -72,7 +72,7 @@ class Mulang::Expectation::V0 < Mulang::Expectation::Standard
72
72
  end
73
73
 
74
74
  def as_v2_use
75
- 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)
76
76
  end
77
77
 
78
78
  def as_v2_declare(simple_type)
@@ -1,43 +1,95 @@
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
+ def ast(content, **options)
24
+ Mulang.analyse(ast_analysis(content, **options), **options)['outputAst'] rescue nil
25
+ end
26
+
27
+ def ast_analysis(content, **options)
28
+ base_analysis content, {includeOutputAst: true}, **options
29
+ end
30
+
31
+ private
32
+
33
+ def base_analysis(content, spec, **options)
12
34
  {
13
- sample: { tag: 'CodeSample', language: @language, content: content },
14
- spec: { expectations: [], smellsSet: { tag: 'NoSmells' }, includeIntermediateLanguage: true }
35
+ sample: sample(content),
36
+ spec: {
37
+ expectations: [],
38
+ smellsSet: { tag: 'NoSmells' },
39
+ includeOutputAst: false,
40
+ normalizationOptions: normalization_options(options)
41
+ }.merge(spec).compact
15
42
  }
16
43
  end
44
+ end
45
+
46
+ class Native < Base
47
+ attr_accessor :name
48
+
49
+ def initialize(language_name)
50
+ @name = language_name
51
+ end
17
52
 
18
53
  def sample(content)
19
54
  {
20
55
  tag: 'CodeSample',
21
- language: @language,
56
+ language: @name,
22
57
  content: content
23
58
  }
24
59
  end
25
60
  end
26
61
 
27
- class External
28
- def initialize(&tool)
62
+ class External < Base
63
+ attr_accessor :name
64
+
65
+ def initialize(language_name = nil, &tool)
66
+ @name = language_name
29
67
  @tool = block_given? ? tool : proc { |it| it }
30
68
  end
31
69
 
32
- def ast(content)
33
- @tool.call(content) rescue nil
70
+ def ast(content, **args)
71
+ if args[:serialization]
72
+ super
73
+ else
74
+ call_tool content
75
+ end
34
76
  end
35
77
 
36
78
  def sample(content)
37
79
  {
38
80
  tag: 'MulangSample',
39
- ast: ast(content)
81
+ ast: call_tool(content)
40
82
  }
41
83
  end
84
+
85
+ def base_analysis(*)
86
+ super.deep_merge(spec: {originalLanguage: @name}.compact)
87
+ end
88
+
89
+ private
90
+
91
+ def call_tool(content)
92
+ @tool.call(content) rescue nil
93
+ end
42
94
  end
43
95
  end