diff --git a/semantic.cabal b/semantic.cabal index 47b8a8e30..78b9c3a01 100644 --- a/semantic.cabal +++ b/semantic.cabal @@ -51,6 +51,7 @@ library , Data.Abstract.Live , Data.Abstract.ModuleTable , Data.Abstract.Number + , Data.Abstract.Path , Data.Abstract.Type , Data.Abstract.Value -- General datatype definitions & generic algorithms @@ -99,6 +100,7 @@ library , Language.JSON.Assignment , Language.Ruby.Grammar , Language.Ruby.Assignment + , Language.Ruby.Syntax , Language.TypeScript.Assignment , Language.TypeScript.Grammar , Language.TypeScript.Syntax @@ -208,6 +210,7 @@ test-suite test other-modules: Assigning.Assignment.Spec , Analysis.Go.Spec , Analysis.Python.Spec + , Analysis.Ruby.Spec , Analysis.TypeScript.Spec , Data.Diff.Spec , Data.Functor.Classes.Generic.Spec diff --git a/src/Analysis/Declaration.hs b/src/Analysis/Declaration.hs index bf4a727b1..c83a0d6a1 100644 --- a/src/Analysis/Declaration.hs +++ b/src/Analysis/Declaration.hs @@ -18,6 +18,7 @@ import Data.Abstract.FreeVariables import qualified Data.Syntax as Syntax import qualified Data.Syntax.Declaration as Declaration import qualified Data.Syntax.Expression as Expression +import qualified Language.Ruby.Syntax as Ruby.Syntax import qualified Data.Text as T import qualified Data.Text.Encoding as T import qualified Language.Markdown.Syntax as Markdown @@ -98,54 +99,59 @@ instance CustomHasDeclaration whole Declaration.Function where -- Do not summarize anonymous functions | isEmpty identifierAnn = Nothing -- Named functions - | otherwise = Just $ FunctionDeclaration (getSource identifierAnn) (getFunctionSource blob (In ann decl)) blobLanguage - where getSource = toText . flip Source.slice blobSource . getField - isEmpty = (== 0) . rangeLength . getField + | otherwise = Just $ FunctionDeclaration (getSource blobSource identifierAnn) (getFunctionSource blob (In ann decl)) blobLanguage + where isEmpty = (== 0) . rangeLength . getField -- | Produce a 'MethodDeclaration' for 'Declaration.Method' nodes. If the method’s receiver is non-empty (defined as having a non-empty 'Range'), the 'declarationIdentifier' will be formatted as 'receiver.method_name'; otherwise it will be simply 'method_name'. instance CustomHasDeclaration whole Declaration.Method where customToDeclaration blob@Blob{..} ann decl@(Declaration.Method _ (Term (In receiverAnn receiverF), _) (Term (In identifierAnn _), _) _ _) -- Methods without a receiver - | isEmpty receiverAnn = Just $ MethodDeclaration (getSource identifierAnn) (getMethodSource blob (In ann decl)) blobLanguage Nothing + | isEmpty receiverAnn = Just $ MethodDeclaration (getSource blobSource identifierAnn) (getMethodSource blob (In ann decl)) blobLanguage Nothing -- Methods with a receiver type and an identifier (e.g. (a *Type) in Go). | blobLanguage == Just Go - , [ _, Term (In receiverType _) ] <- toList receiverF = Just $ MethodDeclaration (getSource identifierAnn) (getMethodSource blob (In ann decl)) blobLanguage (Just (getSource receiverType)) + , [ _, Term (In receiverType _) ] <- toList receiverF = Just $ MethodDeclaration (getSource blobSource identifierAnn) (getMethodSource blob (In ann decl)) blobLanguage (Just (getSource blobSource receiverType)) -- Methods with a receiver (class methods) are formatted like `receiver.method_name` - | otherwise = Just $ MethodDeclaration (getSource identifierAnn) (getMethodSource blob (In ann decl)) blobLanguage (Just (getSource receiverAnn)) - where getSource = toText . flip Source.slice blobSource . getField - isEmpty = (== 0) . rangeLength . getField + | otherwise = Just $ MethodDeclaration (getSource blobSource identifierAnn) (getMethodSource blob (In ann decl)) blobLanguage (Just (getSource blobSource receiverAnn)) + where isEmpty = (== 0) . rangeLength . getField -- | Produce a 'ClassDeclaration' for 'Declaration.Class' nodes. instance CustomHasDeclaration whole Declaration.Class where customToDeclaration blob@Blob{..} ann decl@(Declaration.Class _ (Term (In identifierAnn _), _) _ _) - -- Classes - = Just $ ClassDeclaration (getSource identifierAnn) (getClassSource blob (In ann decl)) blobLanguage - where getSource = toText . flip Source.slice blobSource . getField + = Just $ ClassDeclaration (getSource blobSource identifierAnn) (getClassSource blob (In ann decl)) blobLanguage instance CustomHasDeclaration (Union fs) Declaration.Import where customToDeclaration Blob{..} _ (Declaration.Import (Term (In fromAnn _), _) symbols _) - = Just $ ImportDeclaration ((stripQuotes . getSource) fromAnn) "" (fmap getSymbol symbols) blobLanguage + = Just $ ImportDeclaration ((stripQuotes . getSource blobSource) fromAnn) "" (fmap getSymbol symbols) blobLanguage where - stripQuotes = T.dropAround (`elem` ['"', '\'']) - getSource = toText . flip Source.slice blobSource . getField getSymbol = let f = (T.decodeUtf8 . friendlyName) in bimap f f instance (Syntax.Identifier :< fs) => CustomHasDeclaration (Union fs) Declaration.QualifiedImport where customToDeclaration Blob{..} _ (Declaration.QualifiedImport (Term (In fromAnn _), _) (Term (In aliasAnn aliasF), _) symbols) - | Just (Syntax.Identifier alias) <- prj aliasF = Just $ ImportDeclaration ((stripQuotes . getSource) fromAnn) (toName alias) (fmap getSymbol symbols) blobLanguage - | otherwise = Just $ ImportDeclaration ((stripQuotes . getSource) fromAnn) (getSource aliasAnn) (fmap getSymbol symbols) blobLanguage + | Just (Syntax.Identifier alias) <- prj aliasF = Just $ ImportDeclaration ((stripQuotes . getSource blobSource) fromAnn) (toName alias) (fmap getSymbol symbols) blobLanguage + | otherwise = Just $ ImportDeclaration ((stripQuotes . getSource blobSource) fromAnn) (getSource blobSource aliasAnn) (fmap getSymbol symbols) blobLanguage where - stripQuotes = T.dropAround (`elem` ['"', '\'']) - getSource = toText . flip Source.slice blobSource . getField getSymbol = bimap toName toName toName = T.decodeUtf8 . friendlyName instance CustomHasDeclaration (Union fs) Declaration.SideEffectImport where customToDeclaration Blob{..} _ (Declaration.SideEffectImport (Term (In fromAnn _), _) _) - = Just $ ImportDeclaration ((stripQuotes . getSource) fromAnn) "" [] blobLanguage - where - stripQuotes = T.dropAround (`elem` ['"', '\'']) - getSource = toText . flip Source.slice blobSource . getField + = Just $ ImportDeclaration ((stripQuotes . getSource blobSource) fromAnn) "" [] blobLanguage + +instance CustomHasDeclaration (Union fs) Ruby.Syntax.Require where + customToDeclaration Blob{..} _ (Ruby.Syntax.Require _ (Term (In fromAnn _), _)) + = Just $ ImportDeclaration ((stripQuotes . getSource blobSource) fromAnn) "" [] blobLanguage + +instance CustomHasDeclaration (Union fs) Ruby.Syntax.Load where + customToDeclaration Blob{..} _ (Ruby.Syntax.Load ((Term (In fromArgs _), _):_)) + = Just $ ImportDeclaration ((stripQuotes . getSource blobSource) fromArgs) "" [] blobLanguage + customToDeclaration Blob{..} _ (Ruby.Syntax.Load _) + = Nothing + +getSource :: HasField fields Range => Source -> Record fields -> Text +getSource blobSource = toText . flip Source.slice blobSource . getField + +stripQuotes :: Text -> Text +stripQuotes = T.dropAround (`elem` ['"', '\'']) instance (Syntax.Identifier :< fs, Expression.MemberAccess :< fs) => CustomHasDeclaration (Union fs) Expression.Call where customToDeclaration Blob{..} _ (Expression.Call _ (Term (In fromAnn fromF), _) _ _) @@ -185,6 +191,7 @@ type family DeclarationStrategy syntax where DeclarationStrategy Declaration.Import = 'Custom DeclarationStrategy Declaration.QualifiedImport = 'Custom DeclarationStrategy Declaration.SideEffectImport = 'Custom + DeclarationStrategy Ruby.Syntax.Require = 'Custom DeclarationStrategy Declaration.Method = 'Custom DeclarationStrategy Markdown.Heading = 'Custom DeclarationStrategy Expression.Call = 'Custom diff --git a/src/Control/Abstract/Value.hs b/src/Control/Abstract/Value.hs index 5b8f408e1..b1dd94667 100644 --- a/src/Control/Abstract/Value.hs +++ b/src/Control/Abstract/Value.hs @@ -56,7 +56,7 @@ class (Monad m, Show value) => MonadValue value m where -- necessary to satisfy implementation details of Haskell left/right shift, -- but it's fine, since these are only ever operating on integral values. liftBitwise2 :: (forall a . (Integral a, Bits a) => a -> a -> a) - -> (value -> value -> m value) + -> (value -> value -> m value) -- | Construct an abstract boolean value. boolean :: Bool -> m value @@ -80,6 +80,9 @@ class (Monad m, Show value) => MonadValue value m where -- | Construct an array of zero or more values. array :: [value] -> m value +-- | Extract a 'ByteString' from a given value. + asString :: value -> m ByteString + -- | Eliminate boolean values. TODO: s/boolean/truthy ifthenelse :: value -> m a -> m a -> m a @@ -143,9 +146,12 @@ instance ( Monad m rational = pure . injValue . Value.Rational . Ratio multiple = pure . injValue . Value.Tuple - array = pure . injValue . Value.Array + asString v + | Just (Value.String n) <- prjValue v = pure n + | otherwise = fail ("expected " <> show v <> " to be a string") + ifthenelse cond if' else' | Just (Boolean b) <- prjValue cond = if b then if' else else' | otherwise = fail ("not defined for non-boolean conditions: " <> show cond) diff --git a/src/Data/Abstract/FreeVariables.hs b/src/Data/Abstract/FreeVariables.hs index 55138f9e9..fbfbfc079 100644 --- a/src/Data/Abstract/FreeVariables.hs +++ b/src/Data/Abstract/FreeVariables.hs @@ -5,16 +5,24 @@ import Prologue import Data.Term import Data.ByteString (intercalate) import qualified Data.List.NonEmpty as NonEmpty +import Data.Abstract.Path -- | The type of variable names. type Name = NonEmpty ByteString +-- | Construct a qualified name from a 'ByteString' name :: ByteString -> Name name x = x :| [] +-- | Construct a qualified name from a list of 'ByteString's qualifiedName :: [ByteString] -> Name qualifiedName = NonEmpty.fromList +-- | Construct a qualified 'Name' from a `/` delimited path. +pathToQualifiedName :: ByteString -> Name +pathToQualifiedName = qualifiedName . splitOnPathSeparator + +-- | User friendly 'ByteString' of a qualified 'Name'. friendlyName :: Name -> ByteString friendlyName xs = intercalate "." (NonEmpty.toList xs) diff --git a/src/Data/Abstract/Path.hs b/src/Data/Abstract/Path.hs new file mode 100644 index 000000000..ce29e69bd --- /dev/null +++ b/src/Data/Abstract/Path.hs @@ -0,0 +1,24 @@ +module Data.Abstract.Path where + +import Prologue +import qualified Data.ByteString.Char8 as BC +import qualified Data.ByteString as B +import Data.Char (ord) + +-- | Split a 'ByteString' path on `/`, stripping quotes and any `./` prefix. +splitOnPathSeparator :: ByteString -> [ByteString] +splitOnPathSeparator = splitOnPathSeparator' id + +splitOnPathSeparator' :: (ByteString -> ByteString) -> ByteString -> [ByteString] +splitOnPathSeparator' f = BC.split '/' . f . dropRelativePrefix . stripQuotes + +stripQuotes :: ByteString -> ByteString +stripQuotes = B.filter (/= fromIntegral (ord '\"')) + +dropRelativePrefix :: ByteString -> ByteString +dropRelativePrefix = BC.dropWhile (== '/') . BC.dropWhile (== '.') + +dropExtension :: ByteString -> ByteString +dropExtension path = case BC.split '.' path of + [] -> path + xs -> BC.intercalate "." (Prelude.init xs) diff --git a/src/Data/Syntax.hs b/src/Data/Syntax.hs index 5264cd286..eee18eab8 100644 --- a/src/Data/Syntax.hs +++ b/src/Data/Syntax.hs @@ -107,7 +107,7 @@ instance Ord1 Identifier where liftCompare = genericLiftCompare instance Show1 Identifier where liftShowsPrec = genericLiftShowsPrec instance Evaluatable Identifier where - eval (Identifier name) = lookupWith deref name >>= maybe (fail ("free variable: " <> show name)) pure + eval (Identifier name) = lookupWith deref name >>= maybe (fail ("free variable: " <> show (friendlyName name))) pure instance FreeVariables1 Identifier where liftFreeVariables _ (Identifier x) = Set.singleton x diff --git a/src/Language/Go/Assignment.hs b/src/Language/Go/Assignment.hs index 7bfad97b8..57441a432 100644 --- a/src/Language/Go/Assignment.hs +++ b/src/Language/Go/Assignment.hs @@ -8,9 +8,7 @@ module Language.Go.Assignment import Assigning.Assignment hiding (Assignment, Error) import Data.Abstract.FreeVariables -import qualified Data.ByteString.Char8 as BC -import qualified Data.ByteString as B -import Data.Char (ord) +import Data.Abstract.Path import Data.Record import Data.Syntax (contextualize, emptyTerm, parseError, handleError, infixContext, makeTerm, makeTerm', makeTerm'', makeTerm1) import Language.Go.Grammar as Grammar @@ -395,7 +393,7 @@ importDeclaration = makeTerm'' <$> symbol ImportDeclaration <*> children (manyTe namedImport = inj <$> (flip Declaration.QualifiedImport <$> packageIdentifier <*> importFromPath <*> pure []) -- `import "lib/Math"` plainImport = inj <$> (symbol InterpretedStringLiteral >>= \loc -> do - names <- pathToNames <$> source + names <- splitOnPathSeparator <$> source let from = makeTerm loc (Syntax.Identifier (qualifiedName names)) let alias = makeTerm loc (Syntax.Identifier (name (last names))) -- Go takes `import "lib/Math"` and uses `Math` as the qualified name (e.g. `Math.Sin()`) Declaration.QualifiedImport <$> pure from <*> pure alias <*> pure []) @@ -406,9 +404,6 @@ importDeclaration = makeTerm'' <$> symbol ImportDeclaration <*> children (manyTe importSpec = makeTerm' <$> symbol ImportSpec <*> children (sideEffectImport <|> dotImport <|> namedImport <|> plainImport) importSpecList = makeTerm <$> symbol ImportSpecList <*> children (manyTerm (importSpec <|> comment)) importFromPath = makeTerm <$> symbol InterpretedStringLiteral <*> (Syntax.Identifier <$> (pathToQualifiedName <$> source)) - pathToQualifiedName = qualifiedName . pathToNames - pathToNames = BC.split '/' . (BC.dropWhile (== '/')) . (BC.dropWhile (== '.')) . stripQuotes - stripQuotes = B.filter (/= (fromIntegral (ord '\"'))) indexExpression :: Assignment indexExpression = makeTerm <$> symbol IndexExpression <*> children (Expression.Subscript <$> expression <*> manyTerm expression) diff --git a/src/Language/Ruby/Assignment.hs b/src/Language/Ruby/Assignment.hs index cb6271506..8157e9262 100644 --- a/src/Language/Ruby/Assignment.hs +++ b/src/Language/Ruby/Assignment.hs @@ -21,13 +21,13 @@ import qualified Data.Syntax.Expression as Expression import qualified Data.Syntax.Literal as Literal import qualified Data.Syntax.Statement as Statement import qualified Data.Term as Term +import qualified Language.Ruby.Syntax as Ruby.Syntax -- | The type of Ruby syntax. type Syntax = '[ Comment.Comment , Declaration.Class , Declaration.Function - , Declaration.Import , Declaration.Method , Declaration.Module , Expression.Arithmetic @@ -74,6 +74,8 @@ type Syntax = '[ , Syntax.Error , Syntax.Identifier , Syntax.Program + , Ruby.Syntax.Require + , Ruby.Syntax.Load , [] ] @@ -295,15 +297,20 @@ pair :: Assignment pair = makeTerm <$> symbol Pair <*> children (Literal.KeyValue <$> expression <*> (expression <|> emptyTerm)) methodCall :: Assignment -methodCall = makeTerm' <$> symbol MethodCall <*> children (require <|> regularCall) +methodCall = makeTerm' <$> symbol MethodCall <*> children (require <|> load <|> regularCall) where regularCall = inj <$> (Expression.Call <$> pure [] <*> expression <*> args <*> (block <|> emptyTerm)) require = inj <$> (symbol Identifier *> do s <- source - guard (elem s ["autoload", "load", "require", "require_relative"]) - Declaration.Import <$> args' <*> pure [] <*> emptyTerm) + guard (elem s ["require", "require_relative"]) + Ruby.Syntax.Require (s == "require_relative") <$> nameExpression) + load = inj <$> (symbol Identifier *> do + s <- source + guard (elem s ["load"]) + Ruby.Syntax.Load <$> loadArgs) args = (symbol ArgumentList <|> symbol ArgumentListWithParens) *> children (many expression) <|> pure [] - args' = makeTerm'' <$> (symbol ArgumentList <|> symbol ArgumentListWithParens) <*> children (many expression) <|> emptyTerm + loadArgs = (symbol ArgumentList <|> symbol ArgumentListWithParens) *> children (some expression) + nameExpression = (symbol ArgumentList <|> symbol ArgumentListWithParens) *> children expression call :: Assignment call = makeTerm <$> symbol Call <*> children (Expression.MemberAccess <$> expression <*> (expression <|> args)) diff --git a/src/Language/Ruby/Syntax.hs b/src/Language/Ruby/Syntax.hs new file mode 100644 index 000000000..753f3273b --- /dev/null +++ b/src/Language/Ruby/Syntax.hs @@ -0,0 +1,54 @@ +{-# LANGUAGE DeriveAnyClass #-} +module Language.Ruby.Syntax where + +import Control.Monad (unless) +import Control.Abstract.Value (MonadValue) +import Data.Abstract.Environment +import Data.Abstract.Evaluatable +import Data.Abstract.Path +import Data.Abstract.Value (LocationFor) +import Diffing.Algorithm +import Prelude hiding (fail) +import Prologue +import qualified Data.Map as Map + +data Require a = Require { requireRelative :: Bool, requirePath :: !a } + deriving (Diffable, Eq, Foldable, Functor, GAlign, Generic1, Mergeable, Ord, Show, Traversable, FreeVariables1) + +instance Eq1 Require where liftEq = genericLiftEq +instance Ord1 Require where liftCompare = genericLiftCompare +instance Show1 Require where liftShowsPrec = genericLiftShowsPrec + +instance Evaluatable Require where + eval (Require _ x) = do + name <- pathToQualifiedName <$> (subtermValue x >>= asString) + importedEnv <- isolate (require name) + modifyGlobalEnv (flip (Map.foldrWithKey envInsert) (unEnvironment importedEnv)) + unit + +newtype Load a = Load { loadArgs :: [a] } + deriving (Diffable, Eq, Foldable, Functor, GAlign, Generic1, Mergeable, Ord, Show, Traversable, FreeVariables1) + +instance Eq1 Load where liftEq = genericLiftEq +instance Ord1 Load where liftCompare = genericLiftCompare +instance Show1 Load where liftShowsPrec = genericLiftShowsPrec + +instance Evaluatable Load where + eval (Load [x]) = do + path <- subtermValue x >>= asString + doLoad path False + eval (Load [x, wrap]) = do + path <- subtermValue x >>= asString + shouldWrap <- subtermValue wrap >>= toBool + doLoad path shouldWrap + eval (Load _) = fail "invalid argument supplied to load, path is required" + +doLoad :: (MonadAnalysis term value m, MonadValue value m, Ord (LocationFor value)) => ByteString -> Bool -> m value +doLoad path shouldWrap = do + let name = pathToQualifiedName path + importedEnv <- isolate (load name) + unless shouldWrap $ modifyGlobalEnv (flip (Map.foldrWithKey envInsert) (unEnvironment importedEnv)) + unit + where pathToQualifiedName = qualifiedName . splitOnPathSeparator' dropExtension + +-- TODO: autoload diff --git a/src/Language/TypeScript/Assignment.hs b/src/Language/TypeScript/Assignment.hs index 52f1af3c7..ab0df4765 100644 --- a/src/Language/TypeScript/Assignment.hs +++ b/src/Language/TypeScript/Assignment.hs @@ -9,9 +9,6 @@ module Language.TypeScript.Assignment import Assigning.Assignment hiding (Assignment, Error) import qualified Assigning.Assignment as Assignment import Data.Abstract.FreeVariables -import qualified Data.ByteString as B (filter) -import qualified Data.ByteString.Char8 as BC -import Data.Char (ord) import Data.Record import Data.Syntax (emptyTerm, handleError, parseError, infixContext, makeTerm, makeTerm', makeTerm'', makeTerm1, contextualize, postContextualize) import qualified Data.Syntax as Syntax @@ -676,12 +673,6 @@ importStatement = makeImportTerm <$> symbol Grammar.ImportStatement <*> childr fromClause :: Assignment fromClause = makeTerm <$> symbol Grammar.String <*> (Syntax.Identifier <$> (pathToQualifiedName <$> source)) - where - pathToQualifiedName :: ByteString -> Name - pathToQualifiedName = qualifiedName . BC.split '/' . (BC.dropWhile (== '/')) . (BC.dropWhile (== '.')) . stripQuotes - - stripQuotes :: ByteString -> ByteString - stripQuotes = B.filter (/= (fromIntegral (ord '\"'))) debuggerStatement :: Assignment debuggerStatement = makeTerm <$> symbol Grammar.DebuggerStatement <*> (TypeScript.Syntax.Debugger <$ source) diff --git a/test/Analysis/Go/Spec.hs b/test/Analysis/Go/Spec.hs index 1e2d044ac..7fedf7488 100644 --- a/test/Analysis/Go/Spec.hs +++ b/test/Analysis/Go/Spec.hs @@ -1,4 +1,3 @@ -{-# LANGUAGE TypeApplications #-} module Analysis.Go.Spec (spec) where import Data.Abstract.Value diff --git a/test/Analysis/Python/Spec.hs b/test/Analysis/Python/Spec.hs index 84df0f177..7129ef560 100644 --- a/test/Analysis/Python/Spec.hs +++ b/test/Analysis/Python/Spec.hs @@ -1,4 +1,3 @@ -{-# LANGUAGE TypeApplications #-} module Analysis.Python.Spec (spec) where import Data.Abstract.Value diff --git a/test/Analysis/Ruby/Spec.hs b/test/Analysis/Ruby/Spec.hs new file mode 100644 index 000000000..332ea2725 --- /dev/null +++ b/test/Analysis/Ruby/Spec.hs @@ -0,0 +1,35 @@ +module Analysis.Ruby.Spec (spec) where + +import Data.Abstract.Value +import Data.Map + +import SpecHelpers + + +spec :: Spec +spec = parallel $ do + describe "evalutes Ruby" $ do + it "require_relative" $ do + env <- evaluate "main.rb" + let expectedEnv = Environment $ fromList [ (qualifiedName ["foo"], addr 0) ] + env `shouldBe` expectedEnv + + it "load" $ do + env <- evaluate "load.rb" + let expectedEnv = Environment $ fromList [ (qualifiedName ["foo"], addr 0) ] + env `shouldBe` expectedEnv + + it "load wrap" $ do + res <- evaluate' "load-wrap.rb" + fst res `shouldBe` Left "free variable: \"foo\"" + snd res `shouldBe` Environment (fromList [ ]) + + where + addr = Address . Precise + fixtures = "test/fixtures/ruby/analysis/" + evaluate entry = snd <$> evaluate' entry + evaluate' entry = fst . fst . fst . fst <$> + evaluateFiles rubyParser + [ fixtures <> entry + , fixtures <> "foo.rb" + ] diff --git a/test/Analysis/TypeScript/Spec.hs b/test/Analysis/TypeScript/Spec.hs index b8cf3b7e7..a32348ef8 100644 --- a/test/Analysis/TypeScript/Spec.hs +++ b/test/Analysis/TypeScript/Spec.hs @@ -1,4 +1,3 @@ -{-# LANGUAGE TypeApplications #-} module Analysis.TypeScript.Spec (spec) where import Data.Abstract.Value diff --git a/test/Spec.hs b/test/Spec.hs index 3d606ae9a..1fd244248 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -2,6 +2,7 @@ module Main where import qualified Analysis.Go.Spec import qualified Analysis.Python.Spec +import qualified Analysis.Ruby.Spec import qualified Analysis.TypeScript.Spec import qualified Assigning.Assignment.Spec import qualified Data.Diff.Spec @@ -27,6 +28,7 @@ main = hspec $ do parallel $ do describe "Analysis.Go" Analysis.Go.Spec.spec describe "Analysis.Python" Analysis.Python.Spec.spec + describe "Analysis.Ruby" Analysis.Ruby.Spec.spec describe "Analysis.TypeScript" Analysis.TypeScript.Spec.spec describe "Assigning.Assignment" Assigning.Assignment.Spec.spec describe "Data.Diff" Data.Diff.Spec.spec diff --git a/test/SpecHelpers.hs b/test/SpecHelpers.hs index 568fb4276..7d21c30b4 100644 --- a/test/SpecHelpers.hs +++ b/test/SpecHelpers.hs @@ -11,7 +11,7 @@ module SpecHelpers ( import Data.Abstract.Address as X import Data.Abstract.Environment as X -import Data.Abstract.FreeVariables as X +import Data.Abstract.FreeVariables as X hiding (dropExtension) import Data.Abstract.Heap as X import Data.Abstract.ModuleTable as X import Data.Blob as X diff --git a/test/fixtures/ruby/analysis/foo.rb b/test/fixtures/ruby/analysis/foo.rb new file mode 100644 index 000000000..8917b8d4e --- /dev/null +++ b/test/fixtures/ruby/analysis/foo.rb @@ -0,0 +1,3 @@ +def foo(x) + return x +end diff --git a/test/fixtures/ruby/analysis/load-wrap.rb b/test/fixtures/ruby/analysis/load-wrap.rb new file mode 100644 index 000000000..1db226541 --- /dev/null +++ b/test/fixtures/ruby/analysis/load-wrap.rb @@ -0,0 +1,3 @@ +load "./foo.rb", true + +foo(1) diff --git a/test/fixtures/ruby/analysis/load.rb b/test/fixtures/ruby/analysis/load.rb new file mode 100644 index 000000000..b112b1deb --- /dev/null +++ b/test/fixtures/ruby/analysis/load.rb @@ -0,0 +1,3 @@ +load "./foo.rb" + +foo(1) diff --git a/test/fixtures/ruby/analysis/main.rb b/test/fixtures/ruby/analysis/main.rb new file mode 100644 index 000000000..fcc0cc22a --- /dev/null +++ b/test/fixtures/ruby/analysis/main.rb @@ -0,0 +1,3 @@ +require_relative "foo" + +foo(1) diff --git a/test/fixtures/ruby/require.diffA-B.txt b/test/fixtures/ruby/require.diffA-B.txt index 000634991..ec63bdae0 100644 --- a/test/fixtures/ruby/require.diffA-B.txt +++ b/test/fixtures/ruby/require.diffA-B.txt @@ -1,14 +1,11 @@ (Program - (Import + (Require { (TextElement) - ->(TextElement) } - (Empty)) -{+(Import - {+( - {+(Symbol)+} - {+(TextElement)+})+} - {+(Empty)+})+} -{-(Call + ->(TextElement) }) + (Call + { (Identifier) + ->(Identifier) } + {+(Symbol)+} + {+(TextElement)+} {-(Identifier)-} - {-(Identifier)-} - {-(Empty)-})-}) + (Empty))) diff --git a/test/fixtures/ruby/require.diffB-A.txt b/test/fixtures/ruby/require.diffB-A.txt index 7ea78ad11..890c906af 100644 --- a/test/fixtures/ruby/require.diffB-A.txt +++ b/test/fixtures/ruby/require.diffB-A.txt @@ -1,14 +1,11 @@ (Program - (Import + (Require { (TextElement) - ->(TextElement) } - (Empty)) -{+(Call + ->(TextElement) }) + (Call + { (Identifier) + ->(Identifier) } {+(Identifier)+} - {+(Identifier)+} - {+(Empty)+})+} -{-(Import - {-( - {-(Symbol)-} - {-(TextElement)-})-} - {-(Empty)-})-}) + {-(Symbol)-} + {-(TextElement)-} + (Empty))) diff --git a/test/fixtures/ruby/require.parseA.txt b/test/fixtures/ruby/require.parseA.txt index e4960dafe..f33f28750 100644 --- a/test/fixtures/ruby/require.parseA.txt +++ b/test/fixtures/ruby/require.parseA.txt @@ -1,7 +1,6 @@ (Program - (Import - (TextElement) - (Empty)) + (Require + (TextElement)) (Call (Identifier) (Identifier) diff --git a/test/fixtures/ruby/require.parseB.txt b/test/fixtures/ruby/require.parseB.txt index 1203a16f7..638ed1768 100644 --- a/test/fixtures/ruby/require.parseB.txt +++ b/test/fixtures/ruby/require.parseB.txt @@ -1,9 +1,8 @@ (Program - (Import + (Require + (TextElement)) + (Call + (Identifier) + (Symbol) (TextElement) - (Empty)) - (Import - ( - (Symbol) - (TextElement)) (Empty)))