mirror of
https://github.com/github/semantic.git
synced 2024-12-20 05:11:44 +03:00
Merge pull request #1574 from github/ruby-imports
Eval Ruby require and load
This commit is contained in:
commit
e40a5e7c71
@ -51,6 +51,7 @@ library
|
|||||||
, Data.Abstract.Live
|
, Data.Abstract.Live
|
||||||
, Data.Abstract.ModuleTable
|
, Data.Abstract.ModuleTable
|
||||||
, Data.Abstract.Number
|
, Data.Abstract.Number
|
||||||
|
, Data.Abstract.Path
|
||||||
, Data.Abstract.Type
|
, Data.Abstract.Type
|
||||||
, Data.Abstract.Value
|
, Data.Abstract.Value
|
||||||
-- General datatype definitions & generic algorithms
|
-- General datatype definitions & generic algorithms
|
||||||
@ -99,6 +100,7 @@ library
|
|||||||
, Language.JSON.Assignment
|
, Language.JSON.Assignment
|
||||||
, Language.Ruby.Grammar
|
, Language.Ruby.Grammar
|
||||||
, Language.Ruby.Assignment
|
, Language.Ruby.Assignment
|
||||||
|
, Language.Ruby.Syntax
|
||||||
, Language.TypeScript.Assignment
|
, Language.TypeScript.Assignment
|
||||||
, Language.TypeScript.Grammar
|
, Language.TypeScript.Grammar
|
||||||
, Language.TypeScript.Syntax
|
, Language.TypeScript.Syntax
|
||||||
@ -208,6 +210,7 @@ test-suite test
|
|||||||
other-modules: Assigning.Assignment.Spec
|
other-modules: Assigning.Assignment.Spec
|
||||||
, Analysis.Go.Spec
|
, Analysis.Go.Spec
|
||||||
, Analysis.Python.Spec
|
, Analysis.Python.Spec
|
||||||
|
, Analysis.Ruby.Spec
|
||||||
, Analysis.TypeScript.Spec
|
, Analysis.TypeScript.Spec
|
||||||
, Data.Diff.Spec
|
, Data.Diff.Spec
|
||||||
, Data.Functor.Classes.Generic.Spec
|
, Data.Functor.Classes.Generic.Spec
|
||||||
|
@ -18,6 +18,7 @@ import Data.Abstract.FreeVariables
|
|||||||
import qualified Data.Syntax as Syntax
|
import qualified Data.Syntax as Syntax
|
||||||
import qualified Data.Syntax.Declaration as Declaration
|
import qualified Data.Syntax.Declaration as Declaration
|
||||||
import qualified Data.Syntax.Expression as Expression
|
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 as T
|
||||||
import qualified Data.Text.Encoding as T
|
import qualified Data.Text.Encoding as T
|
||||||
import qualified Language.Markdown.Syntax as Markdown
|
import qualified Language.Markdown.Syntax as Markdown
|
||||||
@ -98,54 +99,59 @@ instance CustomHasDeclaration whole Declaration.Function where
|
|||||||
-- Do not summarize anonymous functions
|
-- Do not summarize anonymous functions
|
||||||
| isEmpty identifierAnn = Nothing
|
| isEmpty identifierAnn = Nothing
|
||||||
-- Named functions
|
-- Named functions
|
||||||
| otherwise = Just $ FunctionDeclaration (getSource identifierAnn) (getFunctionSource blob (In ann decl)) blobLanguage
|
| otherwise = Just $ FunctionDeclaration (getSource blobSource identifierAnn) (getFunctionSource blob (In ann decl)) blobLanguage
|
||||||
where getSource = toText . flip Source.slice blobSource . getField
|
where isEmpty = (== 0) . rangeLength . getField
|
||||||
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'.
|
-- | 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
|
instance CustomHasDeclaration whole Declaration.Method where
|
||||||
customToDeclaration blob@Blob{..} ann decl@(Declaration.Method _ (Term (In receiverAnn receiverF), _) (Term (In identifierAnn _), _) _ _)
|
customToDeclaration blob@Blob{..} ann decl@(Declaration.Method _ (Term (In receiverAnn receiverF), _) (Term (In identifierAnn _), _) _ _)
|
||||||
-- Methods without a receiver
|
-- 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).
|
-- Methods with a receiver type and an identifier (e.g. (a *Type) in Go).
|
||||||
| blobLanguage == Just 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`
|
-- 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))
|
| otherwise = Just $ MethodDeclaration (getSource blobSource identifierAnn) (getMethodSource blob (In ann decl)) blobLanguage (Just (getSource blobSource receiverAnn))
|
||||||
where getSource = toText . flip Source.slice blobSource . getField
|
where isEmpty = (== 0) . rangeLength . getField
|
||||||
isEmpty = (== 0) . rangeLength . getField
|
|
||||||
|
|
||||||
-- | Produce a 'ClassDeclaration' for 'Declaration.Class' nodes.
|
-- | Produce a 'ClassDeclaration' for 'Declaration.Class' nodes.
|
||||||
instance CustomHasDeclaration whole Declaration.Class where
|
instance CustomHasDeclaration whole Declaration.Class where
|
||||||
customToDeclaration blob@Blob{..} ann decl@(Declaration.Class _ (Term (In identifierAnn _), _) _ _)
|
customToDeclaration blob@Blob{..} ann decl@(Declaration.Class _ (Term (In identifierAnn _), _) _ _)
|
||||||
-- Classes
|
= Just $ ClassDeclaration (getSource blobSource identifierAnn) (getClassSource blob (In ann decl)) blobLanguage
|
||||||
= Just $ ClassDeclaration (getSource identifierAnn) (getClassSource blob (In ann decl)) blobLanguage
|
|
||||||
where getSource = toText . flip Source.slice blobSource . getField
|
|
||||||
|
|
||||||
instance CustomHasDeclaration (Union fs) Declaration.Import where
|
instance CustomHasDeclaration (Union fs) Declaration.Import where
|
||||||
customToDeclaration Blob{..} _ (Declaration.Import (Term (In fromAnn _), _) symbols _)
|
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
|
where
|
||||||
stripQuotes = T.dropAround (`elem` ['"', '\''])
|
|
||||||
getSource = toText . flip Source.slice blobSource . getField
|
|
||||||
getSymbol = let f = (T.decodeUtf8 . friendlyName) in bimap f f
|
getSymbol = let f = (T.decodeUtf8 . friendlyName) in bimap f f
|
||||||
|
|
||||||
instance (Syntax.Identifier :< fs) => CustomHasDeclaration (Union fs) Declaration.QualifiedImport where
|
instance (Syntax.Identifier :< fs) => CustomHasDeclaration (Union fs) Declaration.QualifiedImport where
|
||||||
customToDeclaration Blob{..} _ (Declaration.QualifiedImport (Term (In fromAnn _), _) (Term (In aliasAnn aliasF), _) symbols)
|
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
|
| Just (Syntax.Identifier alias) <- prj aliasF = Just $ ImportDeclaration ((stripQuotes . getSource blobSource) fromAnn) (toName alias) (fmap getSymbol symbols) blobLanguage
|
||||||
| otherwise = Just $ ImportDeclaration ((stripQuotes . getSource) fromAnn) (getSource aliasAnn) (fmap getSymbol symbols) blobLanguage
|
| otherwise = Just $ ImportDeclaration ((stripQuotes . getSource blobSource) fromAnn) (getSource blobSource aliasAnn) (fmap getSymbol symbols) blobLanguage
|
||||||
where
|
where
|
||||||
stripQuotes = T.dropAround (`elem` ['"', '\''])
|
|
||||||
getSource = toText . flip Source.slice blobSource . getField
|
|
||||||
getSymbol = bimap toName toName
|
getSymbol = bimap toName toName
|
||||||
toName = T.decodeUtf8 . friendlyName
|
toName = T.decodeUtf8 . friendlyName
|
||||||
|
|
||||||
instance CustomHasDeclaration (Union fs) Declaration.SideEffectImport where
|
instance CustomHasDeclaration (Union fs) Declaration.SideEffectImport where
|
||||||
customToDeclaration Blob{..} _ (Declaration.SideEffectImport (Term (In fromAnn _), _) _)
|
customToDeclaration Blob{..} _ (Declaration.SideEffectImport (Term (In fromAnn _), _) _)
|
||||||
= Just $ ImportDeclaration ((stripQuotes . getSource) fromAnn) "" [] blobLanguage
|
= Just $ ImportDeclaration ((stripQuotes . getSource blobSource) fromAnn) "" [] blobLanguage
|
||||||
where
|
|
||||||
stripQuotes = T.dropAround (`elem` ['"', '\''])
|
instance CustomHasDeclaration (Union fs) Ruby.Syntax.Require where
|
||||||
getSource = toText . flip Source.slice blobSource . getField
|
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
|
instance (Syntax.Identifier :< fs, Expression.MemberAccess :< fs) => CustomHasDeclaration (Union fs) Expression.Call where
|
||||||
customToDeclaration Blob{..} _ (Expression.Call _ (Term (In fromAnn fromF), _) _ _)
|
customToDeclaration Blob{..} _ (Expression.Call _ (Term (In fromAnn fromF), _) _ _)
|
||||||
@ -185,6 +191,7 @@ type family DeclarationStrategy syntax where
|
|||||||
DeclarationStrategy Declaration.Import = 'Custom
|
DeclarationStrategy Declaration.Import = 'Custom
|
||||||
DeclarationStrategy Declaration.QualifiedImport = 'Custom
|
DeclarationStrategy Declaration.QualifiedImport = 'Custom
|
||||||
DeclarationStrategy Declaration.SideEffectImport = 'Custom
|
DeclarationStrategy Declaration.SideEffectImport = 'Custom
|
||||||
|
DeclarationStrategy Ruby.Syntax.Require = 'Custom
|
||||||
DeclarationStrategy Declaration.Method = 'Custom
|
DeclarationStrategy Declaration.Method = 'Custom
|
||||||
DeclarationStrategy Markdown.Heading = 'Custom
|
DeclarationStrategy Markdown.Heading = 'Custom
|
||||||
DeclarationStrategy Expression.Call = 'Custom
|
DeclarationStrategy Expression.Call = 'Custom
|
||||||
|
@ -56,7 +56,7 @@ class (Monad m, Show value) => MonadValue value m where
|
|||||||
-- necessary to satisfy implementation details of Haskell left/right shift,
|
-- necessary to satisfy implementation details of Haskell left/right shift,
|
||||||
-- but it's fine, since these are only ever operating on integral values.
|
-- but it's fine, since these are only ever operating on integral values.
|
||||||
liftBitwise2 :: (forall a . (Integral a, Bits a) => a -> a -> a)
|
liftBitwise2 :: (forall a . (Integral a, Bits a) => a -> a -> a)
|
||||||
-> (value -> value -> m value)
|
-> (value -> value -> m value)
|
||||||
|
|
||||||
-- | Construct an abstract boolean value.
|
-- | Construct an abstract boolean value.
|
||||||
boolean :: Bool -> m 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.
|
-- | Construct an array of zero or more values.
|
||||||
array :: [value] -> m value
|
array :: [value] -> m value
|
||||||
|
|
||||||
|
-- | Extract a 'ByteString' from a given value.
|
||||||
|
asString :: value -> m ByteString
|
||||||
|
|
||||||
-- | Eliminate boolean values. TODO: s/boolean/truthy
|
-- | Eliminate boolean values. TODO: s/boolean/truthy
|
||||||
ifthenelse :: value -> m a -> m a -> m a
|
ifthenelse :: value -> m a -> m a -> m a
|
||||||
|
|
||||||
@ -143,9 +146,12 @@ instance ( Monad m
|
|||||||
rational = pure . injValue . Value.Rational . Ratio
|
rational = pure . injValue . Value.Rational . Ratio
|
||||||
|
|
||||||
multiple = pure . injValue . Value.Tuple
|
multiple = pure . injValue . Value.Tuple
|
||||||
|
|
||||||
array = pure . injValue . Value.Array
|
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'
|
ifthenelse cond if' else'
|
||||||
| Just (Boolean b) <- prjValue cond = if b then if' else else'
|
| Just (Boolean b) <- prjValue cond = if b then if' else else'
|
||||||
| otherwise = fail ("not defined for non-boolean conditions: " <> show cond)
|
| otherwise = fail ("not defined for non-boolean conditions: " <> show cond)
|
||||||
|
@ -5,16 +5,24 @@ import Prologue
|
|||||||
import Data.Term
|
import Data.Term
|
||||||
import Data.ByteString (intercalate)
|
import Data.ByteString (intercalate)
|
||||||
import qualified Data.List.NonEmpty as NonEmpty
|
import qualified Data.List.NonEmpty as NonEmpty
|
||||||
|
import Data.Abstract.Path
|
||||||
|
|
||||||
-- | The type of variable names.
|
-- | The type of variable names.
|
||||||
type Name = NonEmpty ByteString
|
type Name = NonEmpty ByteString
|
||||||
|
|
||||||
|
-- | Construct a qualified name from a 'ByteString'
|
||||||
name :: ByteString -> Name
|
name :: ByteString -> Name
|
||||||
name x = x :| []
|
name x = x :| []
|
||||||
|
|
||||||
|
-- | Construct a qualified name from a list of 'ByteString's
|
||||||
qualifiedName :: [ByteString] -> Name
|
qualifiedName :: [ByteString] -> Name
|
||||||
qualifiedName = NonEmpty.fromList
|
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 :: Name -> ByteString
|
||||||
friendlyName xs = intercalate "." (NonEmpty.toList xs)
|
friendlyName xs = intercalate "." (NonEmpty.toList xs)
|
||||||
|
|
||||||
|
24
src/Data/Abstract/Path.hs
Normal file
24
src/Data/Abstract/Path.hs
Normal file
@ -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)
|
@ -107,7 +107,7 @@ instance Ord1 Identifier where liftCompare = genericLiftCompare
|
|||||||
instance Show1 Identifier where liftShowsPrec = genericLiftShowsPrec
|
instance Show1 Identifier where liftShowsPrec = genericLiftShowsPrec
|
||||||
|
|
||||||
instance Evaluatable Identifier where
|
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
|
instance FreeVariables1 Identifier where
|
||||||
liftFreeVariables _ (Identifier x) = Set.singleton x
|
liftFreeVariables _ (Identifier x) = Set.singleton x
|
||||||
|
@ -8,9 +8,7 @@ module Language.Go.Assignment
|
|||||||
|
|
||||||
import Assigning.Assignment hiding (Assignment, Error)
|
import Assigning.Assignment hiding (Assignment, Error)
|
||||||
import Data.Abstract.FreeVariables
|
import Data.Abstract.FreeVariables
|
||||||
import qualified Data.ByteString.Char8 as BC
|
import Data.Abstract.Path
|
||||||
import qualified Data.ByteString as B
|
|
||||||
import Data.Char (ord)
|
|
||||||
import Data.Record
|
import Data.Record
|
||||||
import Data.Syntax (contextualize, emptyTerm, parseError, handleError, infixContext, makeTerm, makeTerm', makeTerm'', makeTerm1)
|
import Data.Syntax (contextualize, emptyTerm, parseError, handleError, infixContext, makeTerm, makeTerm', makeTerm'', makeTerm1)
|
||||||
import Language.Go.Grammar as Grammar
|
import Language.Go.Grammar as Grammar
|
||||||
@ -395,7 +393,7 @@ importDeclaration = makeTerm'' <$> symbol ImportDeclaration <*> children (manyTe
|
|||||||
namedImport = inj <$> (flip Declaration.QualifiedImport <$> packageIdentifier <*> importFromPath <*> pure [])
|
namedImport = inj <$> (flip Declaration.QualifiedImport <$> packageIdentifier <*> importFromPath <*> pure [])
|
||||||
-- `import "lib/Math"`
|
-- `import "lib/Math"`
|
||||||
plainImport = inj <$> (symbol InterpretedStringLiteral >>= \loc -> do
|
plainImport = inj <$> (symbol InterpretedStringLiteral >>= \loc -> do
|
||||||
names <- pathToNames <$> source
|
names <- splitOnPathSeparator <$> source
|
||||||
let from = makeTerm loc (Syntax.Identifier (qualifiedName names))
|
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()`)
|
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 [])
|
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)
|
importSpec = makeTerm' <$> symbol ImportSpec <*> children (sideEffectImport <|> dotImport <|> namedImport <|> plainImport)
|
||||||
importSpecList = makeTerm <$> symbol ImportSpecList <*> children (manyTerm (importSpec <|> comment))
|
importSpecList = makeTerm <$> symbol ImportSpecList <*> children (manyTerm (importSpec <|> comment))
|
||||||
importFromPath = makeTerm <$> symbol InterpretedStringLiteral <*> (Syntax.Identifier <$> (pathToQualifiedName <$> source))
|
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 :: Assignment
|
||||||
indexExpression = makeTerm <$> symbol IndexExpression <*> children (Expression.Subscript <$> expression <*> manyTerm expression)
|
indexExpression = makeTerm <$> symbol IndexExpression <*> children (Expression.Subscript <$> expression <*> manyTerm expression)
|
||||||
|
@ -21,13 +21,13 @@ import qualified Data.Syntax.Expression as Expression
|
|||||||
import qualified Data.Syntax.Literal as Literal
|
import qualified Data.Syntax.Literal as Literal
|
||||||
import qualified Data.Syntax.Statement as Statement
|
import qualified Data.Syntax.Statement as Statement
|
||||||
import qualified Data.Term as Term
|
import qualified Data.Term as Term
|
||||||
|
import qualified Language.Ruby.Syntax as Ruby.Syntax
|
||||||
|
|
||||||
-- | The type of Ruby syntax.
|
-- | The type of Ruby syntax.
|
||||||
type Syntax = '[
|
type Syntax = '[
|
||||||
Comment.Comment
|
Comment.Comment
|
||||||
, Declaration.Class
|
, Declaration.Class
|
||||||
, Declaration.Function
|
, Declaration.Function
|
||||||
, Declaration.Import
|
|
||||||
, Declaration.Method
|
, Declaration.Method
|
||||||
, Declaration.Module
|
, Declaration.Module
|
||||||
, Expression.Arithmetic
|
, Expression.Arithmetic
|
||||||
@ -74,6 +74,8 @@ type Syntax = '[
|
|||||||
, Syntax.Error
|
, Syntax.Error
|
||||||
, Syntax.Identifier
|
, Syntax.Identifier
|
||||||
, Syntax.Program
|
, Syntax.Program
|
||||||
|
, Ruby.Syntax.Require
|
||||||
|
, Ruby.Syntax.Load
|
||||||
, []
|
, []
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -295,15 +297,20 @@ pair :: Assignment
|
|||||||
pair = makeTerm <$> symbol Pair <*> children (Literal.KeyValue <$> expression <*> (expression <|> emptyTerm))
|
pair = makeTerm <$> symbol Pair <*> children (Literal.KeyValue <$> expression <*> (expression <|> emptyTerm))
|
||||||
|
|
||||||
methodCall :: Assignment
|
methodCall :: Assignment
|
||||||
methodCall = makeTerm' <$> symbol MethodCall <*> children (require <|> regularCall)
|
methodCall = makeTerm' <$> symbol MethodCall <*> children (require <|> load <|> regularCall)
|
||||||
where
|
where
|
||||||
regularCall = inj <$> (Expression.Call <$> pure [] <*> expression <*> args <*> (block <|> emptyTerm))
|
regularCall = inj <$> (Expression.Call <$> pure [] <*> expression <*> args <*> (block <|> emptyTerm))
|
||||||
require = inj <$> (symbol Identifier *> do
|
require = inj <$> (symbol Identifier *> do
|
||||||
s <- source
|
s <- source
|
||||||
guard (elem s ["autoload", "load", "require", "require_relative"])
|
guard (elem s ["require", "require_relative"])
|
||||||
Declaration.Import <$> args' <*> pure [] <*> emptyTerm)
|
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 = (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 :: Assignment
|
||||||
call = makeTerm <$> symbol Call <*> children (Expression.MemberAccess <$> expression <*> (expression <|> args))
|
call = makeTerm <$> symbol Call <*> children (Expression.MemberAccess <$> expression <*> (expression <|> args))
|
||||||
|
54
src/Language/Ruby/Syntax.hs
Normal file
54
src/Language/Ruby/Syntax.hs
Normal file
@ -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
|
@ -9,9 +9,6 @@ module Language.TypeScript.Assignment
|
|||||||
import Assigning.Assignment hiding (Assignment, Error)
|
import Assigning.Assignment hiding (Assignment, Error)
|
||||||
import qualified Assigning.Assignment as Assignment
|
import qualified Assigning.Assignment as Assignment
|
||||||
import Data.Abstract.FreeVariables
|
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.Record
|
||||||
import Data.Syntax (emptyTerm, handleError, parseError, infixContext, makeTerm, makeTerm', makeTerm'', makeTerm1, contextualize, postContextualize)
|
import Data.Syntax (emptyTerm, handleError, parseError, infixContext, makeTerm, makeTerm', makeTerm'', makeTerm1, contextualize, postContextualize)
|
||||||
import qualified Data.Syntax as Syntax
|
import qualified Data.Syntax as Syntax
|
||||||
@ -676,12 +673,6 @@ importStatement = makeImportTerm <$> symbol Grammar.ImportStatement <*> childr
|
|||||||
|
|
||||||
fromClause :: Assignment
|
fromClause :: Assignment
|
||||||
fromClause = makeTerm <$> symbol Grammar.String <*> (Syntax.Identifier <$> (pathToQualifiedName <$> source))
|
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 :: Assignment
|
||||||
debuggerStatement = makeTerm <$> symbol Grammar.DebuggerStatement <*> (TypeScript.Syntax.Debugger <$ source)
|
debuggerStatement = makeTerm <$> symbol Grammar.DebuggerStatement <*> (TypeScript.Syntax.Debugger <$ source)
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
{-# LANGUAGE TypeApplications #-}
|
|
||||||
module Analysis.Go.Spec (spec) where
|
module Analysis.Go.Spec (spec) where
|
||||||
|
|
||||||
import Data.Abstract.Value
|
import Data.Abstract.Value
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
{-# LANGUAGE TypeApplications #-}
|
|
||||||
module Analysis.Python.Spec (spec) where
|
module Analysis.Python.Spec (spec) where
|
||||||
|
|
||||||
import Data.Abstract.Value
|
import Data.Abstract.Value
|
||||||
|
35
test/Analysis/Ruby/Spec.hs
Normal file
35
test/Analysis/Ruby/Spec.hs
Normal file
@ -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"
|
||||||
|
]
|
@ -1,4 +1,3 @@
|
|||||||
{-# LANGUAGE TypeApplications #-}
|
|
||||||
module Analysis.TypeScript.Spec (spec) where
|
module Analysis.TypeScript.Spec (spec) where
|
||||||
|
|
||||||
import Data.Abstract.Value
|
import Data.Abstract.Value
|
||||||
|
@ -2,6 +2,7 @@ module Main where
|
|||||||
|
|
||||||
import qualified Analysis.Go.Spec
|
import qualified Analysis.Go.Spec
|
||||||
import qualified Analysis.Python.Spec
|
import qualified Analysis.Python.Spec
|
||||||
|
import qualified Analysis.Ruby.Spec
|
||||||
import qualified Analysis.TypeScript.Spec
|
import qualified Analysis.TypeScript.Spec
|
||||||
import qualified Assigning.Assignment.Spec
|
import qualified Assigning.Assignment.Spec
|
||||||
import qualified Data.Diff.Spec
|
import qualified Data.Diff.Spec
|
||||||
@ -27,6 +28,7 @@ main = hspec $ do
|
|||||||
parallel $ do
|
parallel $ do
|
||||||
describe "Analysis.Go" Analysis.Go.Spec.spec
|
describe "Analysis.Go" Analysis.Go.Spec.spec
|
||||||
describe "Analysis.Python" Analysis.Python.Spec.spec
|
describe "Analysis.Python" Analysis.Python.Spec.spec
|
||||||
|
describe "Analysis.Ruby" Analysis.Ruby.Spec.spec
|
||||||
describe "Analysis.TypeScript" Analysis.TypeScript.Spec.spec
|
describe "Analysis.TypeScript" Analysis.TypeScript.Spec.spec
|
||||||
describe "Assigning.Assignment" Assigning.Assignment.Spec.spec
|
describe "Assigning.Assignment" Assigning.Assignment.Spec.spec
|
||||||
describe "Data.Diff" Data.Diff.Spec.spec
|
describe "Data.Diff" Data.Diff.Spec.spec
|
||||||
|
@ -11,7 +11,7 @@ module SpecHelpers (
|
|||||||
|
|
||||||
import Data.Abstract.Address as X
|
import Data.Abstract.Address as X
|
||||||
import Data.Abstract.Environment 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.Heap as X
|
||||||
import Data.Abstract.ModuleTable as X
|
import Data.Abstract.ModuleTable as X
|
||||||
import Data.Blob as X
|
import Data.Blob as X
|
||||||
|
3
test/fixtures/ruby/analysis/foo.rb
vendored
Normal file
3
test/fixtures/ruby/analysis/foo.rb
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
def foo(x)
|
||||||
|
return x
|
||||||
|
end
|
3
test/fixtures/ruby/analysis/load-wrap.rb
vendored
Normal file
3
test/fixtures/ruby/analysis/load-wrap.rb
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
load "./foo.rb", true
|
||||||
|
|
||||||
|
foo(1)
|
3
test/fixtures/ruby/analysis/load.rb
vendored
Normal file
3
test/fixtures/ruby/analysis/load.rb
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
load "./foo.rb"
|
||||||
|
|
||||||
|
foo(1)
|
3
test/fixtures/ruby/analysis/main.rb
vendored
Normal file
3
test/fixtures/ruby/analysis/main.rb
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
require_relative "foo"
|
||||||
|
|
||||||
|
foo(1)
|
19
test/fixtures/ruby/require.diffA-B.txt
vendored
19
test/fixtures/ruby/require.diffA-B.txt
vendored
@ -1,14 +1,11 @@
|
|||||||
(Program
|
(Program
|
||||||
(Import
|
(Require
|
||||||
{ (TextElement)
|
{ (TextElement)
|
||||||
->(TextElement) }
|
->(TextElement) })
|
||||||
(Empty))
|
(Call
|
||||||
{+(Import
|
{ (Identifier)
|
||||||
{+(
|
->(Identifier) }
|
||||||
{+(Symbol)+}
|
{+(Symbol)+}
|
||||||
{+(TextElement)+})+}
|
{+(TextElement)+}
|
||||||
{+(Empty)+})+}
|
|
||||||
{-(Call
|
|
||||||
{-(Identifier)-}
|
{-(Identifier)-}
|
||||||
{-(Identifier)-}
|
(Empty)))
|
||||||
{-(Empty)-})-})
|
|
||||||
|
19
test/fixtures/ruby/require.diffB-A.txt
vendored
19
test/fixtures/ruby/require.diffB-A.txt
vendored
@ -1,14 +1,11 @@
|
|||||||
(Program
|
(Program
|
||||||
(Import
|
(Require
|
||||||
{ (TextElement)
|
{ (TextElement)
|
||||||
->(TextElement) }
|
->(TextElement) })
|
||||||
(Empty))
|
(Call
|
||||||
{+(Call
|
{ (Identifier)
|
||||||
|
->(Identifier) }
|
||||||
{+(Identifier)+}
|
{+(Identifier)+}
|
||||||
{+(Identifier)+}
|
{-(Symbol)-}
|
||||||
{+(Empty)+})+}
|
{-(TextElement)-}
|
||||||
{-(Import
|
(Empty)))
|
||||||
{-(
|
|
||||||
{-(Symbol)-}
|
|
||||||
{-(TextElement)-})-}
|
|
||||||
{-(Empty)-})-})
|
|
||||||
|
5
test/fixtures/ruby/require.parseA.txt
vendored
5
test/fixtures/ruby/require.parseA.txt
vendored
@ -1,7 +1,6 @@
|
|||||||
(Program
|
(Program
|
||||||
(Import
|
(Require
|
||||||
(TextElement)
|
(TextElement))
|
||||||
(Empty))
|
|
||||||
(Call
|
(Call
|
||||||
(Identifier)
|
(Identifier)
|
||||||
(Identifier)
|
(Identifier)
|
||||||
|
11
test/fixtures/ruby/require.parseB.txt
vendored
11
test/fixtures/ruby/require.parseB.txt
vendored
@ -1,9 +1,8 @@
|
|||||||
(Program
|
(Program
|
||||||
(Import
|
(Require
|
||||||
|
(TextElement))
|
||||||
|
(Call
|
||||||
|
(Identifier)
|
||||||
|
(Symbol)
|
||||||
(TextElement)
|
(TextElement)
|
||||||
(Empty))
|
|
||||||
(Import
|
|
||||||
(
|
|
||||||
(Symbol)
|
|
||||||
(TextElement))
|
|
||||||
(Empty)))
|
(Empty)))
|
||||||
|
Loading…
Reference in New Issue
Block a user