Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature strict mode #253

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions mulang.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,13 @@ library
Language.Mulang.Analyzer.Analysis
Language.Mulang.Analyzer.Analysis.Json
Language.Mulang.Analyzer.Autocorrector
Language.Mulang.Analyzer.CustomExpectationsAnalyzer
Language.Mulang.Analyzer.DomainLanguageCompiler
Language.Mulang.Analyzer.EdlQueryCompiler
Language.Mulang.Analyzer.ExpectationsEvaluator
Language.Mulang.Analyzer.ExpectationsAnalyzer
Language.Mulang.Analyzer.ExpectationsCompiler
Language.Mulang.Analyzer.CustomExpectationsAnalyzer
Language.Mulang.Analyzer.EdlQueryCompiler
Language.Mulang.Analyzer.Finding
Language.Mulang.Analyzer.FragmentParser
Language.Mulang.Analyzer.SignaturesAnalyzer
Language.Mulang.Analyzer.SignatureStyleCompiler
Expand Down
5 changes: 4 additions & 1 deletion spec/CustomExpectationsAnalyzerSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,12 @@ spec = describe "ExpectationsAnalyzer" $ do

(run JavaScript "if (true) {}; if(true) {}; if (false) {}" "expectation: count (UsesIf) >= 3") `shouldReturn` ok

it "rejects unsupported counters" $ do
(run JavaScript "if (true) {}; " "expectation: count (declares) >= 3") `shouldReturn` AnalysisFailed "Can not count over Declares with matcher Unmatching"

it "is case sensitive in standard syntax" $ do
(run JavaScript "" "expectation: UsesIf") `shouldReturn` nok
(run JavaScript "" "expectation: usesif") `shouldReturn` ok
(run JavaScript "" "expectation: usesif") `shouldReturn` AnalysisFailed "Unknown inspection Usesif with matcher Unmatching"

it "accepts titlecase in standard syntax" $ do
(run JavaScript "" "expectation: UsesIf") `shouldReturn` nok
Expand Down
4 changes: 2 additions & 2 deletions spec/ExpectationsAnalyzerSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ spec = describe "ExpectationsAnalyzer" $ do

it "evaluates unknown basic expectations" $ do
let hasTurtle = Expectation "x" "HasTurtle"
(run Haskell "x = 2" [hasTurtle]) `shouldReturn` (result [passed hasTurtle] [])
(run Haskell "x = 2" [hasTurtle]) `shouldReturn` AnalysisFailed "Unknown inspection HasTurtle with matcher Unmatching"

it "evaluates unknown basic negated expectations" $ do
let notHasTurtle = Expectation "x" "Not:HasTurtle"
(run Haskell "x = 2" [notHasTurtle]) `shouldReturn` (result [passed notHasTurtle] [])
(run Haskell "x = 2" [notHasTurtle]) `shouldReturn` AnalysisFailed "Unknown inspection HasTurtle with matcher Unmatching"

it "evaluates empty expectations" $ do
(run Haskell "x = 2" []) `shouldReturn` (result [] [])
Expand Down
2 changes: 1 addition & 1 deletion spec/ExpectationsCompilerSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Language.Mulang.Parsers.Java

spec :: Spec
spec = do
let run code scope inspection = compileExpectation (Expectation scope inspection) code
let run code scope inspection | Right f <- compileExpectation (Expectation scope inspection) = f code

it "works with DeclaresEntryPoint" $ do
run (hs "f x = 2") "*" "DeclaresEntryPoint" `shouldBe` False
Expand Down
21 changes: 16 additions & 5 deletions src/Language/Mulang/Analyzer.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module Language.Mulang.Analyzer (

import Language.Mulang
import Language.Mulang.Analyzer.Analysis hiding (Inspection)
import Language.Mulang.Analyzer.ExpectationsEvaluator (lenientMode, strictMode)
import Language.Mulang.Analyzer.DomainLanguageCompiler (compileDomainLanguage)
import Language.Mulang.Analyzer.ExpectationsAnalyzer (analyseExpectations)
import Language.Mulang.Analyzer.CustomExpectationsAnalyzer (analyseCustomExpectations)
Expand All @@ -28,15 +29,25 @@ analyse' (Analysis sample spec) = analyseSample . parseFragment $ sample

analyseAst :: Expression -> AnalysisSpec -> IO AnalysisResult
analyseAst ast spec = do
let evaluator = compileExpectationsEvaluator (expectationsEvaluator spec)
domaingLang <- compileDomainLanguage (domainLanguage spec)
testResults <- analyseTests ast (testAnalysisType spec)
return $ AnalysisCompleted (analyseExpectations ast (expectations spec) ++ analyseCustomExpectations ast (customExpectations spec))
(analyseSmells ast domaingLang (smellsSet spec))
(analyseSignatures ast (signatureAnalysisType spec))
testResults
(analyzeIntermediateLanguage ast spec)
return $ buildAnalysis (analyseExpectations evaluator ast (expectations spec))
(analyseCustomExpectations evaluator ast (customExpectations spec))
(analyseSmells ast domaingLang (smellsSet spec))
(analyseSignatures ast (signatureAnalysisType spec))
testResults
(analyzeIntermediateLanguage ast spec)

buildAnalysis (Left message) _ = \_ _ _ _ -> AnalysisFailed message
buildAnalysis _ (Left message) = \_ _ _ _ -> AnalysisFailed message
buildAnalysis (Right expectationResults) (Right customExpectationResults) = AnalysisCompleted (expectationResults ++ customExpectationResults)

analyzeIntermediateLanguage :: Expression -> AnalysisSpec -> Maybe Expression
analyzeIntermediateLanguage ast spec
| fromMaybe False (includeIntermediateLanguage spec) = Just ast
| otherwise = Nothing


compileExpectationsEvaluator (Just StrictMode) = strictMode
compileExpectationsEvaluator _ = lenientMode
14 changes: 10 additions & 4 deletions src/Language/Mulang/Analyzer/Analysis.hs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ module Language.Mulang.Analyzer.Analysis (
Smell,
SmellsSet(..),
TestAnalysisType(..),
ExpectationsEvaluator(..),

AnalysisResult(..),
ExpectationResult(..)) where
Expand Down Expand Up @@ -78,7 +79,8 @@ data AnalysisSpec = AnalysisSpec {
domainLanguage :: Maybe DomainLanguage,
includeIntermediateLanguage :: Maybe Bool,
originalLanguage :: Maybe Language,
autocorrectionRules :: Maybe AutocorrectionRules
autocorrectionRules :: Maybe AutocorrectionRules,
expectationsEvaluator :: Maybe ExpectationsEvaluator
} deriving (Show, Eq, Generic)

data DomainLanguage = DomainLanguage {
Expand All @@ -88,6 +90,10 @@ data DomainLanguage = DomainLanguage {
jargon :: Maybe [String]
} deriving (Show, Eq, Generic)

data ExpectationsEvaluator
= LenientMode
| StrictMode deriving (Show, Eq, Generic)

data CaseStyle
= CamelCase
| SnakeCase
Expand Down Expand Up @@ -175,7 +181,7 @@ allSmellsBut :: [Smell] -> Maybe SmellsSet
allSmellsBut = Just . AllSmells . Just

emptyAnalysisSpec :: AnalysisSpec
emptyAnalysisSpec = AnalysisSpec Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing
emptyAnalysisSpec = AnalysisSpec Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing

emptyAnalysis :: Fragment -> Analysis
emptyAnalysis code = Analysis code emptyAnalysisSpec
Expand All @@ -184,10 +190,10 @@ domainLanguageAnalysis :: Fragment -> DomainLanguage -> Analysis
domainLanguageAnalysis code domainLanguage = Analysis code (emptyAnalysisSpec { domainLanguage = Just domainLanguage, smellsSet = allSmells })

customExpectationsAnalysis :: Fragment -> String -> Analysis
customExpectationsAnalysis code es = Analysis code (emptyAnalysisSpec { customExpectations = Just es })
customExpectationsAnalysis code es = Analysis code (emptyAnalysisSpec { customExpectations = Just es, expectationsEvaluator = Just StrictMode })

expectationsAnalysis :: Fragment -> [Expectation] -> Analysis
expectationsAnalysis code es = Analysis code (emptyAnalysisSpec { expectations = Just es })
expectationsAnalysis code es = Analysis code (emptyAnalysisSpec { expectations = Just es, expectationsEvaluator = Just StrictMode })

smellsAnalysis :: Fragment -> Maybe SmellsSet -> Analysis
smellsAnalysis code set = Analysis code (emptyAnalysisSpec { smellsSet = set })
Expand Down
1 change: 1 addition & 0 deletions src/Language/Mulang/Analyzer/Analysis/Json.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ instance FromJSON CaseStyle

instance FromJSON SignatureAnalysisType
instance FromJSON SignatureStyle
instance FromJSON ExpectationsEvaluator

instance FromJSON NormalizationOptions
instance FromJSON SequenceSortMode
Expand Down
13 changes: 9 additions & 4 deletions src/Language/Mulang/Analyzer/CustomExpectationsAnalyzer.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@ module Language.Mulang.Analyzer.CustomExpectationsAnalyzer (
import Data.Maybe (fromMaybe)

import Language.Mulang
import Language.Mulang.Analyzer.Finding
import Language.Mulang.Analyzer.ExpectationsEvaluator (ExpectationsEvaluator)
import Language.Mulang.Analyzer.Analysis (customExpectationResult, ExpectationResult(..))
import Language.Mulang.Analyzer.EdlQueryCompiler (compileTopQuery)

import Language.Mulang.Edl (parseExpectations, Expectation (..))

analyseCustomExpectations :: Expression -> Maybe String -> [ExpectationResult]
analyseCustomExpectations ast = fromMaybe [] . fmap (map (runExpectation ast) . parseExpectations)
analyseCustomExpectations :: ExpectationsEvaluator -> Expression -> Maybe String -> Finding [ExpectationResult]
analyseCustomExpectations evaluator ast = fromMaybe (return []) . fmap (analyseCustomExpectations' evaluator ast)

runExpectation :: Expression -> Expectation -> ExpectationResult
runExpectation ast (Expectation name q) = customExpectationResult name (compileTopQuery q ast)
analyseCustomExpectations' :: ExpectationsEvaluator -> Expression -> String -> Finding [ExpectationResult]
analyseCustomExpectations' evaluator ast = mapFindings (analyseExpectation evaluator ast) . parseExpectations

analyseExpectation :: ExpectationsEvaluator -> Expression -> Expectation -> Finding ExpectationResult
analyseExpectation evaluator ast (Expectation name q) = fmap (customExpectationResult name) compileAndEval
where compileAndEval = evaluator ast (compileTopQuery q)
87 changes: 45 additions & 42 deletions src/Language/Mulang/Analyzer/EdlQueryCompiler.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,23 @@ import Data.Function.Extra (orElse, andAlso, never)
import Language.Mulang
import Language.Mulang.Consult (Consult)
import Language.Mulang.Counter (plus)
import Language.Mulang.Inspector.Primitive (atLeast, atMost, exactly)
import Language.Mulang.Inspector.Primitive (lenient, atLeast, atMost, exactly)
import Language.Mulang.Inspector.Literal (isNil, isNumber, isBool, isChar, isString, isSymbol, isSelf)
import Language.Mulang.Analyzer.Synthesizer (decodeUsageInspection, decodeDeclarationInspection)
import Language.Mulang.Analyzer.Finding (Finding)

import qualified Language.Mulang.Edl.Expectation as E

import Data.Maybe (fromMaybe)
import Data.Either (either)
import Control.Monad.Except
import Data.List.Split (splitOn)

type Scope = (ContextualizedInspection -> ContextualizedInspection, IdentifierPredicate -> IdentifierPredicate)

compileTopQuery :: E.Query -> Inspection
compileTopQuery = fromMaybe (const True) . compileQuery
compileTopQuery :: E.Query -> Finding Inspection
compileTopQuery = compileQuery

compileQuery :: E.Query -> Maybe Inspection
compileQuery :: E.Query -> Finding Inspection
compileQuery (E.Decontextualize query) = compileCQuery id query >>= (return . decontextualize)
compileQuery (E.Within name query) | (scope, p) <- compileWithin name = fmap (decontextualize.scope) (compileCQuery p query)
compileQuery (E.Through name query) | (scope, p) <- compileThrough name = fmap (decontextualize.scope) (compileCQuery p query)
Expand All @@ -40,7 +42,7 @@ scopeFor :: ([Identifier] -> Inspection -> Inspection) -> Identifier -> Scope
scopeFor f name = (contextualized (f names), andAlso (except (last names)))
where names = splitOn "." name

compileCQuery :: (IdentifierPredicate -> IdentifierPredicate) -> E.CQuery -> Maybe ContextualizedInspection
compileCQuery :: (IdentifierPredicate -> IdentifierPredicate) -> E.CQuery -> Finding ContextualizedInspection
compileCQuery pm (E.Inspection i p m) = ($ (compilePredicate pm p)) <$> compileInspection (compileVerb i) m
compileCQuery pm (E.AtLeast n q) = contextualized (atLeast (encode n)) <$> compileTQuery pm q
compileCQuery pm (E.AtMost n q) = contextualized (atMost (encode n)) <$> compileTQuery pm q
Expand All @@ -49,7 +51,7 @@ compileCQuery pm (E.CNot q) = contextualized never <$>
compileCQuery pm (E.CAnd q1 q2) = contextualized2 andAlso <$> compileCQuery pm q1 <*> compileCQuery pm q2
compileCQuery pm (E.COr q1 q2) = contextualized2 orElse <$> compileCQuery pm q1 <*> compileCQuery pm q2

compileTQuery :: (IdentifierPredicate -> IdentifierPredicate) -> E.TQuery -> Maybe ContextualizedCounter
compileTQuery :: (IdentifierPredicate -> IdentifierPredicate) -> E.TQuery -> Finding ContextualizedCounter
compileTQuery pm (E.Counter i p m) = ($ (compilePredicate pm p)) <$> compileCounter (compileVerb i) m
compileTQuery pm (E.Plus q1 q2) = contextualized2 plus <$> (compileTQuery pm q1) <*> (compileTQuery pm q2)

Expand All @@ -69,7 +71,7 @@ compileVerb :: String -> String
compileVerb = concat . map headToUpper . words
where headToUpper (x:xs) = toUpper x : xs

compileCounter :: String -> E.Matcher -> Maybe (ContextualizedBoundCounter)
compileCounter :: String -> E.Matcher -> Finding (ContextualizedBoundCounter)
compileCounter = f
where
f "UsesIf" m = plainMatching countIfs m
Expand All @@ -86,8 +88,9 @@ compileCounter = f
f "DeclaresProcedure" m = boundMatching countProcedures m
f "DeclaresVariable" m = boundMatching countVariables m
f "Calls" m = boundMatching countCalls m
f other m = throwError $ "Can not count over " ++ other ++ " with matcher " ++ show m

compileInspection :: String -> E.Matcher -> Maybe ContextualizedBoundInspection
compileInspection :: String -> E.Matcher -> Finding ContextualizedBoundInspection
compileInspection = f
where
f "Assigns" m = boundMatching assignsMatching m
Expand Down Expand Up @@ -165,49 +168,49 @@ compileInspection = f
f "UsesYield" m = plainMatching usesYieldMatching m
f (primitiveDeclaration -> Just p) E.Unmatching = plain (declaresPrimitive p)
f (primitiveUsage -> Just p) E.Unmatching = plain (usesPrimitive p)
f _ _ = Nothing
f other m = throwError $ "Unknown inspection " ++ other ++ " with matcher " ++ show m

primitiveUsage = decodeUsageInspection
primitiveDeclaration = decodeDeclarationInspection

contextual :: ContextualizedConsult a -> Maybe (ContextualizedBoundConsult a)
contextual = Just . contextualizedBind
contextual :: ContextualizedConsult a -> Finding (ContextualizedBoundConsult a)
contextual = return . contextualizedBind

contextualizedBound :: ContextualizedBoundConsult a -> Maybe (ContextualizedBoundConsult a)
contextualizedBound = Just
contextualizedBound :: ContextualizedBoundConsult a -> Finding (ContextualizedBoundConsult a)
contextualizedBound = return

plain :: Consult a -> Maybe (ContextualizedBoundConsult a)
plain = Just . contextualizedBind . contextualize
plain :: Consult a -> Finding (ContextualizedBoundConsult a)
plain = return . contextualizedBind . contextualize

bound :: BoundConsult a -> Maybe (ContextualizedBoundConsult a)
bound = Just . boundContextualize
bound :: BoundConsult a -> Finding (ContextualizedBoundConsult a)
bound = return . boundContextualize

boundMatching :: (Matcher -> BoundConsult a) -> E.Matcher -> Maybe (ContextualizedBoundConsult a)
boundMatching f m = bound (f (compileMatcher m))
boundMatching :: (Matcher -> BoundConsult a) -> E.Matcher -> Finding (ContextualizedBoundConsult a)
boundMatching f m = (fmap f . compileMatcher) m >>= bound

plainMatching :: (Matcher -> Consult a) -> E.Matcher -> Maybe (ContextualizedBoundConsult a)
plainMatching f m = plain (f (compileMatcher m))
plainMatching :: (Matcher -> Consult a) -> E.Matcher -> Finding (ContextualizedBoundConsult a)
plainMatching f m = (fmap f . compileMatcher) m >>= plain

compileMatcher :: E.Matcher -> Matcher
compileMatcher :: E.Matcher -> Finding Matcher
compileMatcher (E.Matching clauses) = compileClauses clauses
compileMatcher _ = const True
compileMatcher _ = return $ const True

compileClauses :: [E.Clause] -> Matcher
compileClauses = withEvery . f
compileClauses :: [E.Clause] -> Finding Matcher
compileClauses = fmap withEvery . f
where
f :: [E.Clause] -> [Inspection]
f (E.IsAnything:args) = isAnything : (f args)
f (E.IsChar value:args) = isChar value : (f args)
f (E.IsFalse:args) = isBool False : (f args)
f (E.IsLiteral:args) = isLiteral : (f args)
f (E.IsLogic:args) = usesLogic : (f args)
f (E.IsMath:args) = usesMath : (f args)
f (E.IsNil:args) = isNil : (f args)
f (E.IsNonliteral:args) = isNonliteral : (f args)
f (E.IsSelf:args) = isSelf : (f args)
f (E.IsTrue:args) = isBool True : (f args)
f (E.IsNumber value:args) = isNumber value : (f args)
f (E.IsString value:args) = isString value : (f args)
f (E.IsSymbol value:args) = isSymbol value : (f args)
f (E.That expectation:args) = compileTopQuery expectation : (f args)
f [] = []
f :: [E.Clause] -> Finding [Inspection]
f (E.IsAnything:args) = fmap (lenient :) (f args)
f (E.IsChar value:args) = fmap (isChar value :) (f args)
f (E.IsFalse:args) = fmap (isBool False :) (f args)
f (E.IsLiteral:args) = fmap (isLiteral :) (f args)
f (E.IsLogic:args) = fmap (usesLogic :) (f args)
f (E.IsMath:args) = fmap (usesMath :) (f args)
f (E.IsNil:args) = fmap (isNil :) (f args)
f (E.IsNonliteral:args) = fmap (isNonliteral :) (f args)
f (E.IsSelf:args) = fmap (isSelf :) (f args)
f (E.IsTrue:args) = fmap (isBool True :) (f args)
f (E.IsNumber value:args) = fmap (isNumber value :) (f args)
f (E.IsString value:args) = fmap (isString value :) (f args)
f (E.IsSymbol value:args) = fmap (isSymbol value :) (f args)
f (E.That expectation:args) = (:) <$> compileTopQuery expectation <*> (f args)
f [] = return []
12 changes: 7 additions & 5 deletions src/Language/Mulang/Analyzer/ExpectationsAnalyzer.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ module Language.Mulang.Analyzer.ExpectationsAnalyzer (
import Data.Maybe (fromMaybe)

import Language.Mulang
import Language.Mulang.Analyzer.ExpectationsEvaluator (ExpectationsEvaluator)
import Language.Mulang.Analyzer.Finding (Finding, mapFindings)
import Language.Mulang.Analyzer.Analysis (Expectation, ExpectationResult(..))
import Language.Mulang.Analyzer.ExpectationsCompiler (compileExpectation)

analyseExpectations :: Expression -> Maybe [Expectation] -> [ExpectationResult]
analyseExpectations content = map (analyseExpectation content) . (fromMaybe [])
analyseExpectations :: ExpectationsEvaluator -> Expression -> Maybe [Expectation] -> Finding [ExpectationResult]
analyseExpectations evaluator ast = mapFindings (analyseExpectation evaluator ast) . (fromMaybe [])

analyseExpectation :: Expression -> Expectation -> ExpectationResult
analyseExpectation ast e = ExpectationResult e (compileAndEval e)
where compileAndEval e = (compileExpectation e) ast
analyseExpectation :: ExpectationsEvaluator -> Expression -> Expectation -> Finding ExpectationResult
analyseExpectation evaluator ast e = fmap (ExpectationResult e) (compileAndEval e)
where compileAndEval e = evaluator ast (compileExpectation e)

Loading