From 383683320f04b32ecf37a29b6d0956d99fb3b54c Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Wed, 14 Mar 2018 14:29:03 -0700 Subject: [PATCH 01/30] New type wrapper for exports --- src/Analysis/Abstract/Evaluating.hs | 8 +++----- src/Control/Abstract/Analysis.hs | 2 +- src/Control/Abstract/Evaluator.hs | 16 ++++++++++++---- src/Data/Abstract/Environment.hs | 24 ++++++++++++++++-------- src/Data/Abstract/Value.hs | 3 +++ 5 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/Analysis/Abstract/Evaluating.hs b/src/Analysis/Abstract/Evaluating.hs index a25887cbd..4e16fb509 100644 --- a/src/Analysis/Abstract/Evaluating.hs +++ b/src/Analysis/Abstract/Evaluating.hs @@ -14,7 +14,6 @@ import Control.Monad.Effect.Reader import Control.Monad.Effect.State import Data.Abstract.Configuration import Data.Abstract.Evaluatable -import Data.Abstract.Address import Data.Abstract.ModuleTable import Data.Abstract.Value import Data.Blob @@ -84,17 +83,16 @@ type EvaluatingEffects term value , State (StoreFor value) -- The heap , Reader (ModuleTable [term]) -- Cache of unevaluated modules , State (ModuleTable (EnvironmentFor value)) -- Cache of evaluated modules - - , State (Map Name (Name, Maybe (Address (LocationFor value) value))) -- Set of exports + , State (ExportsFor value) -- Exports (used to filter environments when they are imported) ] -instance Members '[State (Map Name (Name, Maybe (Address (LocationFor value) value))), Reader (EnvironmentFor value), State (EnvironmentFor value)] effects => MonadEnvironment value (Evaluating term value effects) where +instance Members '[State (ExportsFor value), Reader (EnvironmentFor value), State (EnvironmentFor value)] effects => MonadEnvironment value (Evaluating term value effects) where getGlobalEnv = raise get putGlobalEnv = raise . put withGlobalEnv s = raise . localState s . lower - addExport key = raise . modify . Map.insert key getExports = raise get + putExports = raise . put withExports s = raise . localState s . lower askLocalEnv = raise ask diff --git a/src/Control/Abstract/Analysis.hs b/src/Control/Abstract/Analysis.hs index 835131f8a..61b66d7b8 100644 --- a/src/Control/Abstract/Analysis.hs +++ b/src/Control/Abstract/Analysis.hs @@ -77,7 +77,7 @@ load name = askModuleTable >>= maybe notFound evalAndCache . moduleTableLookup n -- TODO: If the set of exports is empty because no exports have been -- defined, do we export all terms, or no terms? This behavior varies across -- languages. We need better semantics rather than doing it ad-hoc. - let env' = if Map.null exports then env else bindExports exports env + let env' = if Map.null (unExports exports) then env else bindExports exports env modifyModuleTable (moduleTableInsert name env') (env' <>) <$> evalAndCache xs diff --git a/src/Control/Abstract/Evaluator.hs b/src/Control/Abstract/Evaluator.hs index 6ef756942..819bebc91 100644 --- a/src/Control/Abstract/Evaluator.hs +++ b/src/Control/Abstract/Evaluator.hs @@ -3,6 +3,7 @@ module Control.Abstract.Evaluator ( MonadEvaluator(..) , MonadEnvironment(..) , modifyGlobalEnv +, addExport , MonadStore(..) , modifyStore , assign @@ -12,6 +13,7 @@ module Control.Abstract.Evaluator import Data.Abstract.Address import Data.Abstract.Configuration +import Data.Abstract.Environment import Data.Abstract.FreeVariables import Data.Abstract.ModuleTable import Data.Abstract.Store @@ -43,12 +45,12 @@ class Monad m => MonadEnvironment value m | m -> value where putGlobalEnv :: EnvironmentFor value -> m () withGlobalEnv :: EnvironmentFor value -> m a -> m a - -- | Add an export to the global export state. - addExport :: Name -> (Name, Maybe (Address (LocationFor value) value)) -> m () -- | Get the global export state. - getExports :: m (Map Name (Name, Maybe (Address (LocationFor value) value))) + getExports :: m (ExportsFor value) + -- | Set the global export state. + putExports :: ExportsFor value -> m () -- | Sets the exports state to the given map for the lifetime of the given action. - withExports :: Map Name (Name, Maybe (Address (LocationFor value) value)) -> m a -> m a + withExports :: ExportsFor value -> m a -> m a -- | Retrieve the local environment. askLocalEnv :: m (EnvironmentFor value) @@ -61,6 +63,12 @@ modifyGlobalEnv f = do env <- getGlobalEnv putGlobalEnv $! f env +-- | Add an export to the global export state. +addExport :: MonadEvaluator term value m => Name -> (Name, Maybe (Address (LocationFor value) value)) -> m () +addExport name value = do + exports <- getExports + putExports $! exportInsert name value exports + -- | A 'Monad' abstracting a heap of values. class Monad m => MonadStore value m | m -> value where diff --git a/src/Data/Abstract/Environment.hs b/src/Data/Abstract/Environment.hs index 2d0c6ef3e..e3cddc1c4 100644 --- a/src/Data/Abstract/Environment.hs +++ b/src/Data/Abstract/Environment.hs @@ -1,13 +1,12 @@ {-# LANGUAGE GeneralizedNewtypeDeriving, MultiParamTypeClasses, StandaloneDeriving #-} module Data.Abstract.Environment where -import Prologue import Data.Abstract.Address import Data.Abstract.FreeVariables import Data.Abstract.Live -import qualified Data.Map as Map import Data.Semigroup.Reducer -import qualified Data.Set as Set +import Prologue +import qualified Data.Map as Map -- | A map of names to addresses that represents the evaluation environment. newtype Environment l a = Environment { unEnvironment :: Map.Map Name (Address l a) } @@ -15,6 +14,10 @@ newtype Environment l a = Environment { unEnvironment :: Map.Map Name (Address l deriving instance Reducer (Name, Address l a) (Environment l a) +-- | A map of export names to an alias & address tuple. +newtype Exports l a = Exports { unExports :: Map.Map Name (Name, Maybe (Address l a)) } + deriving (Eq, Foldable, Functor, Generic1, Monoid, Ord, Semigroup, Show, Traversable) + -- | Lookup a 'Name' in the environment. envLookup :: Name -> Environment l a -> Maybe (Address l a) envLookup k = Map.lookup k . unEnvironment @@ -27,11 +30,14 @@ bindEnv :: (Ord l, Foldable t) => t Name -> Environment l a -> Environment l a bindEnv names env = foldMap envForName names where envForName name = maybe mempty (curry unit name) (envLookup name env) -bindExports :: (Ord l) => Map Name (Name, Maybe (Address l a)) -> Environment l a -> Environment l a -bindExports aliases env = Environment pairs +exportInsert :: Name -> (Name, Maybe (Address l a)) -> Exports l a -> Exports l a +exportInsert name value = Exports . Map.insert name value . unExports + +bindExports :: (Ord l) => Exports l a -> Environment l a -> Environment l a +bindExports Exports{..} env = Environment pairs where pairs = Map.foldrWithKey (\name (alias, address) accum -> - maybe accum (\v -> Map.insert alias v accum) (address <|> envLookup name env)) mempty aliases + maybe accum (\v -> Map.insert alias v accum) (address <|> envLookup name env)) mempty unExports -- | Retrieve the 'Live' set of addresses to which the given free variable names are bound. -- @@ -39,11 +45,13 @@ bindExports aliases env = Environment pairs envRoots :: (Ord l, Foldable t) => Environment l a -> t Name -> Live l a envRoots env = foldr ((<>) . maybe mempty liveSingleton . flip envLookup env) mempty -envAll :: (Ord l) => Environment l a -> Live l a -envAll (Environment env) = Live $ Set.fromList (Map.elems env) -- Instances instance Eq l => Eq1 (Environment l) where liftEq = genericLiftEq instance Ord l => Ord1 (Environment l) where liftCompare = genericLiftCompare instance Show l => Show1 (Environment l) where liftShowsPrec = genericLiftShowsPrec + +instance Eq l => Eq1 (Exports l) where liftEq = genericLiftEq +instance Ord l => Ord1 (Exports l) where liftCompare = genericLiftCompare +instance Show l => Show1 (Exports l) where liftShowsPrec = genericLiftShowsPrec diff --git a/src/Data/Abstract/Value.hs b/src/Data/Abstract/Value.hs index ee99a4b79..c330ceb2d 100644 --- a/src/Data/Abstract/Value.hs +++ b/src/Data/Abstract/Value.hs @@ -126,6 +126,9 @@ instance Show1 Tuple where liftShowsPrec = genericLiftShowsPrec -- | The environment for an abstract value type. type EnvironmentFor v = Environment (LocationFor v) v +-- | The exports for an abstract value type. +type ExportsFor v = Exports (LocationFor v) v + -- | The store for an abstract value type. type StoreFor v = Store (LocationFor v) v From dfa51e7621aa65eb17c758dbab85a1117d3ec897 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Wed, 14 Mar 2018 14:43:24 -0700 Subject: [PATCH 02/30] Clean up filtering of exported environment --- src/Control/Abstract/Analysis.hs | 32 +++++++++++++++++++------------- src/Data/Abstract/Environment.hs | 6 ------ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Control/Abstract/Analysis.hs b/src/Control/Abstract/Analysis.hs index 61b66d7b8..315e90802 100644 --- a/src/Control/Abstract/Analysis.hs +++ b/src/Control/Abstract/Analysis.hs @@ -67,19 +67,25 @@ load :: ( MonadAnalysis term value m => ModuleName -> m (EnvironmentFor value) load name = askModuleTable >>= maybe notFound evalAndCache . moduleTableLookup name - where notFound = fail ("cannot load module: " <> show name) - evalAndCache :: (MonadAnalysis term value m, Ord (LocationFor value)) => [term] -> m (EnvironmentFor value) - evalAndCache [] = pure mempty - evalAndCache (x:xs) = do - void $ evaluateModule x - env <- getGlobalEnv - exports <- getExports - -- TODO: If the set of exports is empty because no exports have been - -- defined, do we export all terms, or no terms? This behavior varies across - -- languages. We need better semantics rather than doing it ad-hoc. - let env' = if Map.null (unExports exports) then env else bindExports exports env - modifyModuleTable (moduleTableInsert name env') - (env' <>) <$> evalAndCache xs + where + notFound = fail ("cannot load module: " <> show name) + evalAndCache :: (MonadAnalysis term value m, Ord (LocationFor value)) => [term] -> m (EnvironmentFor value) + evalAndCache [] = pure mempty + evalAndCache (x:xs) = do + void $ evaluateModule x + env <- filterEnv <$> getExports <*> getGlobalEnv + modifyModuleTable (moduleTableInsert name env) + (env <>) <$> evalAndCache xs + + -- TODO: If the set of exports is empty because no exports have been + -- defined, do we export all terms, or no terms? This behavior varies across + -- languages. We need better semantics rather than doing it ad-hoc. + filterEnv :: (Ord l) => Exports l a -> Environment l a -> Environment l a + filterEnv Exports{..} env + | Map.null unExports = env + | otherwise = Environment $ Map.foldrWithKey maybeInsert mempty unExports + where maybeInsert name (alias, address) accum = + maybe accum (\v -> Map.insert alias v accum) (address <|> envLookup name env) -- | Lift a 'SubtermAlgebra' for an underlying analysis into a containing analysis. Use this when defining an analysis which can be composed onto other analyses to ensure that a call to 'analyzeTerm' occurs in the inner analysis and not the outer one. liftAnalyze :: ( Coercible ( m term value (effects :: [* -> *]) value) (t m term value effects value) diff --git a/src/Data/Abstract/Environment.hs b/src/Data/Abstract/Environment.hs index e3cddc1c4..02662e30b 100644 --- a/src/Data/Abstract/Environment.hs +++ b/src/Data/Abstract/Environment.hs @@ -33,12 +33,6 @@ bindEnv names env = foldMap envForName names exportInsert :: Name -> (Name, Maybe (Address l a)) -> Exports l a -> Exports l a exportInsert name value = Exports . Map.insert name value . unExports -bindExports :: (Ord l) => Exports l a -> Environment l a -> Environment l a -bindExports Exports{..} env = Environment pairs - where - pairs = Map.foldrWithKey (\name (alias, address) accum -> - maybe accum (\v -> Map.insert alias v accum) (address <|> envLookup name env)) mempty unExports - -- | Retrieve the 'Live' set of addresses to which the given free variable names are bound. -- -- Unbound names are silently dropped. From 6cda6e5f57686135b43c3e7bce59abd578265178 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Wed, 14 Mar 2018 16:13:27 -0700 Subject: [PATCH 03/30] Refactor wildcard vs. side effect imports --- src/Analysis/Declaration.hs | 8 ++--- src/Data/Syntax/Declaration.hs | 42 ++++++++++++------------ src/Language/Go/Assignment.hs | 29 +++++++++-------- src/Language/Python/Assignment.hs | 24 +++++++------- src/Language/Ruby/Assignment.hs | 2 +- src/Language/TypeScript/Assignment.hs | 47 +++++++++++++-------------- 6 files changed, 78 insertions(+), 74 deletions(-) diff --git a/src/Analysis/Declaration.hs b/src/Analysis/Declaration.hs index 826837f86..bf4a727b1 100644 --- a/src/Analysis/Declaration.hs +++ b/src/Analysis/Declaration.hs @@ -123,7 +123,7 @@ instance CustomHasDeclaration whole Declaration.Class where where getSource = toText . flip Source.slice blobSource . getField 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 where stripQuotes = T.dropAround (`elem` ['"', '\'']) @@ -140,8 +140,8 @@ instance (Syntax.Identifier :< fs) => CustomHasDeclaration (Union fs) Declaratio getSymbol = bimap toName toName toName = T.decodeUtf8 . friendlyName -instance CustomHasDeclaration (Union fs) Declaration.WildcardImport where - customToDeclaration Blob{..} _ (Declaration.WildcardImport (Term (In fromAnn _), _) _) +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` ['"', '\'']) @@ -184,7 +184,7 @@ type family DeclarationStrategy syntax where DeclarationStrategy Declaration.Function = 'Custom DeclarationStrategy Declaration.Import = 'Custom DeclarationStrategy Declaration.QualifiedImport = 'Custom - DeclarationStrategy Declaration.WildcardImport = 'Custom + DeclarationStrategy Declaration.SideEffectImport = 'Custom DeclarationStrategy Declaration.Method = 'Custom DeclarationStrategy Markdown.Heading = 'Custom DeclarationStrategy Expression.Call = 'Custom diff --git a/src/Data/Syntax/Declaration.hs b/src/Data/Syntax/Declaration.hs index de1561c97..e11065168 100644 --- a/src/Data/Syntax/Declaration.hs +++ b/src/Data/Syntax/Declaration.hs @@ -259,6 +259,8 @@ instance Evaluatable DefaultExport where -- | Qualified Import declarations (symbols are qualified in calling environment). +-- +-- If the list of symbols is empty copy and qualify everything to the calling environment. data QualifiedImport a = QualifiedImport { qualifiedImportFrom :: !a, qualifiedImportAlias :: !a, qualifiedImportSymbols :: ![(Name, Name)]} deriving (Diffable, Eq, Foldable, Functor, GAlign, Generic1, Mergeable, Ord, Show, Traversable, FreeVariables1) @@ -275,15 +277,15 @@ instance Evaluatable QualifiedImport where where prefix = freeVariable (subterm alias) symbols = Map.fromList xs - copy = if Map.null symbols then qualifyInsert else directInsert - qualifyInsert k v rest = envInsert (prefix <> k) v rest - directInsert k v rest = maybe rest (\symAlias -> envInsert symAlias v rest) (Map.lookup k symbols) + copy = if Map.null symbols then copyAll else copySymbols + copyAll k v rest = envInsert (prefix <> k) v rest + copySymbols k v rest = maybe rest (\symAlias -> envInsert (prefix <> symAlias) v rest) (Map.lookup k symbols) --- | Import declarations (symbols are added directly to the calling env). +-- | Import declarations (symbols are added directly to the calling environment). -- --- If symbols is empty, just import the module for its side effects. -data Import a = Import { importFrom :: !a, importSymbols :: ![(Name, Name)] } +-- If the list of symbols is empty copy everything to the calling environment. +data Import a = Import { importFrom :: !a, importSymbols :: ![(Name, Name)], importWildcardToken :: !a } deriving (Diffable, Eq, Foldable, Functor, GAlign, Generic1, Mergeable, Ord, Show, Traversable, FreeVariables1) instance Eq1 Import where liftEq = genericLiftEq @@ -291,31 +293,29 @@ instance Ord1 Import where liftCompare = genericLiftCompare instance Show1 Import where liftShowsPrec = genericLiftShowsPrec instance Evaluatable Import where - eval (Import from xs) = do + eval (Import from xs _) = do let moduleName = freeVariable (subterm from) importedEnv <- withGlobalEnv mempty (require moduleName) - modifyGlobalEnv (flip (Map.foldrWithKey directInsert) (unEnvironment importedEnv)) + modifyGlobalEnv (flip (Map.foldrWithKey copy) (unEnvironment importedEnv)) unit where symbols = Map.fromList xs - directInsert k v rest = maybe rest (\symAlias -> envInsert symAlias v rest) (Map.lookup k symbols) + copy = if Map.null symbols then copyAll else copySymbols + copyAll = envInsert + copySymbols k v rest = maybe rest (\symAlias -> envInsert symAlias v rest) (Map.lookup k symbols) - --- | A wildcard import (all symbols are added directly to the calling env) --- --- Import a module updating the importing environments. -data WildcardImport a = WildcardImport { wildcardImportFrom :: !a, wildcardImportToken :: !a } +-- | Side effect only imports (no symbols made available to the calling environment). +data SideEffectImport a = SideEffectImport { sideEffectImportFrom :: !a, sideEffectImportToken :: !a } deriving (Diffable, Eq, Foldable, Functor, GAlign, Generic1, Mergeable, Ord, Show, Traversable, FreeVariables1) -instance Eq1 WildcardImport where liftEq = genericLiftEq -instance Ord1 WildcardImport where liftCompare = genericLiftCompare -instance Show1 WildcardImport where liftShowsPrec = genericLiftShowsPrec +instance Eq1 SideEffectImport where liftEq = genericLiftEq +instance Ord1 SideEffectImport where liftCompare = genericLiftCompare +instance Show1 SideEffectImport where liftShowsPrec = genericLiftShowsPrec -instance Evaluatable WildcardImport where - eval (WildcardImport from _) = do +instance Evaluatable SideEffectImport where + eval (SideEffectImport from _) = do let moduleName = freeVariable (subterm from) - importedEnv <- withGlobalEnv mempty (require moduleName) - modifyGlobalEnv (flip (Map.foldrWithKey envInsert) (unEnvironment importedEnv)) + void $ withGlobalEnv mempty (require moduleName) unit diff --git a/src/Language/Go/Assignment.hs b/src/Language/Go/Assignment.hs index 5ba39a202..7bfad97b8 100644 --- a/src/Language/Go/Assignment.hs +++ b/src/Language/Go/Assignment.hs @@ -33,7 +33,7 @@ type Syntax = , Declaration.Function , Declaration.Import , Declaration.QualifiedImport - , Declaration.WildcardImport + , Declaration.SideEffectImport , Declaration.Method , Declaration.MethodSignature , Declaration.Module @@ -386,23 +386,26 @@ functionDeclaration = makeTerm <$> (symbol FunctionDeclaration <|> symbol FuncL importDeclaration :: Assignment importDeclaration = makeTerm'' <$> symbol ImportDeclaration <*> children (manyTerm (importSpec <|> importSpecList)) where - importSpecList = makeTerm <$> symbol ImportSpecList <*> children (manyTerm (importSpec <|> comment)) - importSpec = makeTerm <$> symbol ImportSpec <*> children sideEffectImport - <|> makeTerm <$> symbol ImportSpec <*> children dotImport - <|> makeTerm <$> symbol ImportSpec <*> children namedImport - <|> makeTerm <$> symbol ImportSpec <*> children plainImport - - dotImport = flip Declaration.WildcardImport <$> (makeTerm <$> symbol Dot <*> (Syntax.Identifier <$> (name <$> source))) <*> importFromPath - sideEffectImport = symbol BlankIdentifier *> source *> (Declaration.Import <$> importFromPath <*> pure []) - namedImport = flip Declaration.QualifiedImport <$> packageIdentifier <*> importFromPath <*> pure [] - plainImport = symbol InterpretedStringLiteral >>= \loc -> do + -- `import . "lib/Math"` + dotImport = inj <$> (makeImport <$> dot <*> importFromPath) + -- dotImport = inj <$> (flip Declaration.Import <$> (symbol Dot *> source *> pure []) <*> importFromPath) + -- `import _ "lib/Math"` + sideEffectImport = inj <$> (flip Declaration.SideEffectImport <$> underscore <*> importFromPath) + -- `import m "lib/Math"` + namedImport = inj <$> (flip Declaration.QualifiedImport <$> packageIdentifier <*> importFromPath <*> pure []) + -- `import "lib/Math"` + plainImport = inj <$> (symbol InterpretedStringLiteral >>= \loc -> do names <- pathToNames <$> 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 [] + Declaration.QualifiedImport <$> pure from <*> pure alias <*> pure []) + makeImport dot path = Declaration.Import path [] dot + dot = makeTerm <$> symbol Dot <*> (Literal.TextElement <$> source) + underscore = makeTerm <$> symbol BlankIdentifier <*> (Literal.TextElement <$> source) + 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 '\"'))) diff --git a/src/Language/Python/Assignment.hs b/src/Language/Python/Assignment.hs index 121b5db16..d6268087a 100644 --- a/src/Language/Python/Assignment.hs +++ b/src/Language/Python/Assignment.hs @@ -36,9 +36,9 @@ type Syntax = , Declaration.Comprehension , Declaration.Decorator , Declaration.Function + , Declaration.Import , Declaration.QualifiedImport , Declaration.Variable - , Declaration.WildcardImport , Expression.Arithmetic , Expression.Boolean , Expression.Bitwise @@ -385,22 +385,24 @@ comment = makeTerm <$> symbol Comment <*> (Comment.Comment <$> source) import' :: Assignment import' = makeTerm'' <$> symbol ImportStatement <*> children (manyTerm (aliasedImport <|> plainImport)) - <|> makeTerm <$> symbol ImportFromStatement <*> children (Declaration.QualifiedImport <$> (identifier <|> emptyTerm) <*> emptyTerm <*> some (aliasImportSymbol <|> importSymbol)) - <|> makeTerm <$> symbol ImportFromStatement <*> children (Declaration.WildcardImport <$> identifier <*> wildcard) + <|> makeTerm <$> symbol ImportFromStatement <*> children (Declaration.Import <$> (identifier <|> emptyTerm) <*> (wildcard <|> (some (aliasImportSymbol <|> importSymbol))) <*> emptyTerm) where + -- `import a as b` + aliasedImport = makeImport <$> symbol AliasedImport <*> children ((,) <$> expression <*> (Just <$> expression)) + -- `import a` + plainImport = makeImport <$> location <*> ((,) <$> identifier <*> pure Nothing) + -- `from a import foo ` + importSymbol = makeNameAliasPair <$> rawIdentifier <*> pure Nothing + -- `from a import foo as bar` + aliasImportSymbol = symbol AliasedImport *> children (makeNameAliasPair <$> rawIdentifier <*> (Just <$> rawIdentifier)) + -- `from a import *` + wildcard = symbol WildcardImport *> source *> pure [] + rawIdentifier = (name <$> identifier') <|> (qualifiedName <$> dottedName') dottedName' = symbol DottedName *> children (some identifier') identifier' = (symbol Identifier <|> symbol Identifier') *> source - makeNameAliasPair from (Just alias) = (from, alias) makeNameAliasPair from Nothing = (from, from) - importSymbol = makeNameAliasPair <$> rawIdentifier <*> pure Nothing - aliasImportSymbol = symbol AliasedImport *> children (makeNameAliasPair <$> rawIdentifier <*> (Just <$> rawIdentifier)) - - wildcard = makeTerm <$> symbol WildcardImport <*> (Syntax.Identifier <$> (name <$> source)) - - aliasedImport = makeImport <$> symbol AliasedImport <*> children ((,) <$> expression <*> (Just <$> expression)) - plainImport = makeImport <$> location <*> ((,) <$> identifier <*> pure Nothing) makeImport loc (from, Just alias) = makeTerm loc (Declaration.QualifiedImport from alias []) makeImport loc (from, Nothing) = makeTerm loc (Declaration.QualifiedImport from from []) diff --git a/src/Language/Ruby/Assignment.hs b/src/Language/Ruby/Assignment.hs index d3531d9e7..cb6271506 100644 --- a/src/Language/Ruby/Assignment.hs +++ b/src/Language/Ruby/Assignment.hs @@ -301,7 +301,7 @@ methodCall = makeTerm' <$> symbol MethodCall <*> children (require <|> regularCa require = inj <$> (symbol Identifier *> do s <- source guard (elem s ["autoload", "load", "require", "require_relative"]) - Declaration.Import <$> args' <*> pure []) + Declaration.Import <$> args' <*> pure [] <*> emptyTerm) args = (symbol ArgumentList <|> symbol ArgumentListWithParens) *> children (many expression) <|> pure [] args' = makeTerm'' <$> (symbol ArgumentList <|> symbol ArgumentListWithParens) <*> children (many expression) <|> emptyTerm diff --git a/src/Language/TypeScript/Assignment.hs b/src/Language/TypeScript/Assignment.hs index 63759a9d4..52f1af3c7 100644 --- a/src/Language/TypeScript/Assignment.hs +++ b/src/Language/TypeScript/Assignment.hs @@ -39,6 +39,7 @@ type Syntax = '[ , Declaration.TypeAlias , Declaration.Import , Declaration.QualifiedImport + , Declaration.SideEffectImport , Declaration.DefaultExport , Declaration.QualifiedExport , Declaration.QualifiedExportFrom @@ -642,35 +643,33 @@ statementIdentifier = makeTerm <$> symbol StatementIdentifier <*> (Syntax.Identi importStatement :: Assignment importStatement = makeImportTerm <$> symbol Grammar.ImportStatement <*> children ((,) <$> importClause <*> term fromClause) - <|> makeImport <$> symbol Grammar.ImportStatement <*> children requireImport - <|> makeImport <$> symbol Grammar.ImportStatement <*> children bareRequireImport + <|> makeTerm' <$> symbol Grammar.ImportStatement <*> children (requireImport <|> sideEffectImport) where - -- Straightforward imports - makeImport loc (Just alias, symbols, from) = makeTerm loc (Declaration.QualifiedImport from alias symbols) - makeImport loc (Nothing, symbols, from) = makeTerm loc (Declaration.Import from symbols) - -- Import a file giving it an alias (e.g. import foo = require "./foo") - requireImport = symbol Grammar.ImportRequireClause *> children ((,,) <$> (Just <$> (term identifier)) <*> pure [] <*> term fromClause) - -- Import a file just for it's side effects (e.g. import "./foo") - bareRequireImport = (,,) <$> (pure Nothing) <*> pure [] <*> term fromClause + -- `import foo = require "./foo"` + requireImport = inj <$> (symbol Grammar.ImportRequireClause *> children (flip Declaration.QualifiedImport <$> (term identifier) <*> term fromClause <*> pure [])) + -- `import "./foo"` + sideEffectImport = inj <$> (Declaration.SideEffectImport <$> term fromClause <*> emptyTerm) + -- `import { bar } from "./foo"` + namedImport = (,,,) <$> pure Prelude.False <*> pure Nothing <*> (symbol Grammar.NamedImports *> children (many importSymbol)) <*> emptyTerm + -- `import defaultMember from "./foo"` + defaultImport = (,,,) <$> pure Prelude.False <*> pure Nothing <*> (pure <$> (makeNameAliasPair <$> rawIdentifier <*> pure Nothing)) <*> emptyTerm + -- `import * as name from "./foo"` + namespaceImport = symbol Grammar.NamespaceImport *> children ((,,,) <$> pure Prelude.True <*> (Just <$> (term identifier)) <*> pure [] <*> emptyTerm) + -- Combinations of the above. + importClause = symbol Grammar.ImportClause *> + children ( + (pure <$> namedImport) + <|> (pure <$> namespaceImport) + <|> ((\a b -> [a, b]) <$> defaultImport <*> (namedImport <|> namespaceImport)) + <|> (pure <$> defaultImport)) - -- Imports with import clauses - makeImportTerm1 loc from (Prelude.True, Just alias, symbols) = makeTerm loc (Declaration.QualifiedImport from alias symbols) - makeImportTerm1 loc from (Prelude.True, Nothing, symbols) = makeTerm loc (Declaration.QualifiedImport from from symbols) - makeImportTerm1 loc from (_, _, symbols) = makeTerm loc (Declaration.Import from symbols) + makeImportTerm1 loc from (Prelude.True, Just alias, symbols, _) = makeTerm loc (Declaration.QualifiedImport from alias symbols) + makeImportTerm1 loc from (Prelude.True, Nothing, symbols, _) = makeTerm loc (Declaration.QualifiedImport from from symbols) + makeImportTerm1 loc from (_, _, symbols, extra) = makeTerm loc (Declaration.Import from symbols extra) makeImportTerm loc ([x], from) = makeImportTerm1 loc from x makeImportTerm loc (xs, from) = makeTerm loc $ fmap (makeImportTerm1 loc from) xs - importClause = symbol Grammar.ImportClause *> - children ( (pure <$> namedImport) - <|> (pure <$> namespaceImport) - <|> ((\a b -> [a, b]) <$> defaultImport <*> (namedImport <|> namespaceImport)) - <|> (pure <$> defaultImport) - ) - namedImport = (,,) <$> pure Prelude.False <*> pure Nothing <*> (symbol Grammar.NamedImports *> children (many importSymbol)) -- import { bar } from "./foo" - defaultImport = (,,) <$> pure Prelude.False <*> pure Nothing <*> (pure <$> (makeNameAliasPair <$> rawIdentifier <*> pure Nothing)) -- import defaultMember from "./foo" - namespaceImport = symbol Grammar.NamespaceImport *> children ((,,) <$> pure Prelude.True <*> (Just <$> (term identifier)) <*> pure []) -- import * as name from "./foo" - importSymbol = symbol Grammar.ImportSpecifier *> children (makeNameAliasPair <$> rawIdentifier <*> (Just <$> rawIdentifier)) - <|> symbol Grammar.ImportSpecifier *> children (makeNameAliasPair <$> rawIdentifier <*> (pure Nothing)) + importSymbol = symbol Grammar.ImportSpecifier *> children (makeNameAliasPair <$> rawIdentifier <*> ((Just <$> rawIdentifier) <|> (pure Nothing))) rawIdentifier = (symbol Identifier <|> symbol Identifier') *> (name <$> source) makeNameAliasPair from (Just alias) = (from, alias) makeNameAliasPair from Nothing = (from, from) From aeb49477a1b5a408d5dc9c0feb1de0f619551408 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Wed, 14 Mar 2018 16:13:41 -0700 Subject: [PATCH 04/30] Update integration fixtures for changes to assignment --- .../grouped-import-declarations.diffA-B.txt | 20 ++-- .../grouped-import-declarations.diffB-A.txt | 20 ++-- .../go/grouped-import-declarations.parseA.txt | 4 +- .../go/grouped-import-declarations.parseB.txt | 4 +- .../go/single-import-declarations.diffA-B.txt | 8 +- .../go/single-import-declarations.diffB-A.txt | 8 +- .../go/single-import-declarations.parseA.txt | 4 +- .../go/single-import-declarations.parseB.txt | 4 +- test/fixtures/javascript/import.diffA-B.txt | 104 +++++++++++------- test/fixtures/javascript/import.diffB-A.txt | 100 ++++++++++------- test/fixtures/javascript/import.parseA.txt | 26 +++-- test/fixtures/javascript/import.parseB.txt | 26 +++-- .../python/import-from-statement.diffA-B.txt | 34 +++--- .../python/import-from-statement.diffB-A.txt | 30 ++--- .../python/import-from-statement.parseA.txt | 14 +-- .../python/import-from-statement.parseB.txt | 16 +-- test/fixtures/ruby/require.diffA-B.txt | 6 +- test/fixtures/ruby/require.diffB-A.txt | 6 +- test/fixtures/ruby/require.parseA.txt | 3 +- test/fixtures/ruby/require.parseB.txt | 6 +- .../typescript/ambient-exports.diffA-B.txt | 42 ++++--- .../typescript/ambient-exports.diffB-A.txt | 32 +++++- test/fixtures/typescript/export.diffA-B.txt | 104 +++++++++++++----- test/fixtures/typescript/export.diffB-A.txt | 96 ++++++++++++---- test/fixtures/typescript/export.parseA.txt | 61 +++++++--- test/fixtures/typescript/export.parseB.txt | 61 +++++++--- test/fixtures/typescript/import.diffA-B.txt | 97 +++++++++------- test/fixtures/typescript/import.diffB-A.txt | 101 ++++++++++------- test/fixtures/typescript/import.parseA.txt | 26 +++-- test/fixtures/typescript/import.parseB.txt | 26 +++-- 30 files changed, 712 insertions(+), 377 deletions(-) diff --git a/test/fixtures/go/grouped-import-declarations.diffA-B.txt b/test/fixtures/go/grouped-import-declarations.diffA-B.txt index 6bcf72719..b7dd5826e 100644 --- a/test/fixtures/go/grouped-import-declarations.diffA-B.txt +++ b/test/fixtures/go/grouped-import-declarations.diffA-B.txt @@ -7,14 +7,18 @@ ->(Identifier) } { (Identifier) ->(Identifier) }) - (WildcardImport - { (Identifier) - ->(Identifier) } - (Identifier)) - (QualifiedImport - { (Identifier) - ->(Identifier) } - (Identifier))) + {+(Import + {+(Identifier)+} + {+(TextElement)+})+} + {+(QualifiedImport + {+(Identifier)+} + {+(Identifier)+})+} + {-(Import + {-(Identifier)-} + {-(TextElement)-})-} + {-(QualifiedImport + {-(Identifier)-} + {-(Identifier)-})-}) (Function (Empty) (Identifier) diff --git a/test/fixtures/go/grouped-import-declarations.diffB-A.txt b/test/fixtures/go/grouped-import-declarations.diffB-A.txt index 6bcf72719..b7dd5826e 100644 --- a/test/fixtures/go/grouped-import-declarations.diffB-A.txt +++ b/test/fixtures/go/grouped-import-declarations.diffB-A.txt @@ -7,14 +7,18 @@ ->(Identifier) } { (Identifier) ->(Identifier) }) - (WildcardImport - { (Identifier) - ->(Identifier) } - (Identifier)) - (QualifiedImport - { (Identifier) - ->(Identifier) } - (Identifier))) + {+(Import + {+(Identifier)+} + {+(TextElement)+})+} + {+(QualifiedImport + {+(Identifier)+} + {+(Identifier)+})+} + {-(Import + {-(Identifier)-} + {-(TextElement)-})-} + {-(QualifiedImport + {-(Identifier)-} + {-(Identifier)-})-}) (Function (Empty) (Identifier) diff --git a/test/fixtures/go/grouped-import-declarations.parseA.txt b/test/fixtures/go/grouped-import-declarations.parseA.txt index c8cd467fd..1a876c61c 100644 --- a/test/fixtures/go/grouped-import-declarations.parseA.txt +++ b/test/fixtures/go/grouped-import-declarations.parseA.txt @@ -5,9 +5,9 @@ (QualifiedImport (Identifier) (Identifier)) - (WildcardImport + (Import (Identifier) - (Identifier)) + (TextElement)) (QualifiedImport (Identifier) (Identifier))) diff --git a/test/fixtures/go/grouped-import-declarations.parseB.txt b/test/fixtures/go/grouped-import-declarations.parseB.txt index c8cd467fd..1a876c61c 100644 --- a/test/fixtures/go/grouped-import-declarations.parseB.txt +++ b/test/fixtures/go/grouped-import-declarations.parseB.txt @@ -5,9 +5,9 @@ (QualifiedImport (Identifier) (Identifier)) - (WildcardImport + (Import (Identifier) - (Identifier)) + (TextElement)) (QualifiedImport (Identifier) (Identifier))) diff --git a/test/fixtures/go/single-import-declarations.diffA-B.txt b/test/fixtures/go/single-import-declarations.diffA-B.txt index 96e0c8a0d..7ec3d901f 100644 --- a/test/fixtures/go/single-import-declarations.diffA-B.txt +++ b/test/fixtures/go/single-import-declarations.diffA-B.txt @@ -4,17 +4,17 @@ {+(QualifiedImport {+(Identifier)+} {+(Identifier)+})+} -{+(WildcardImport +{+(Import {+(Identifier)+} - {+(Identifier)+})+} + {+(TextElement)+})+} (QualifiedImport { (Identifier) ->(Identifier) } { (Identifier) ->(Identifier) }) -{-(WildcardImport +{-(Import {-(Identifier)-} - {-(Identifier)-})-} + {-(TextElement)-})-} {-(QualifiedImport {-(Identifier)-} {-(Identifier)-})-} diff --git a/test/fixtures/go/single-import-declarations.diffB-A.txt b/test/fixtures/go/single-import-declarations.diffB-A.txt index 96e0c8a0d..7ec3d901f 100644 --- a/test/fixtures/go/single-import-declarations.diffB-A.txt +++ b/test/fixtures/go/single-import-declarations.diffB-A.txt @@ -4,17 +4,17 @@ {+(QualifiedImport {+(Identifier)+} {+(Identifier)+})+} -{+(WildcardImport +{+(Import {+(Identifier)+} - {+(Identifier)+})+} + {+(TextElement)+})+} (QualifiedImport { (Identifier) ->(Identifier) } { (Identifier) ->(Identifier) }) -{-(WildcardImport +{-(Import {-(Identifier)-} - {-(Identifier)-})-} + {-(TextElement)-})-} {-(QualifiedImport {-(Identifier)-} {-(Identifier)-})-} diff --git a/test/fixtures/go/single-import-declarations.parseA.txt b/test/fixtures/go/single-import-declarations.parseA.txt index 2d35be6ef..65e7cdd40 100644 --- a/test/fixtures/go/single-import-declarations.parseA.txt +++ b/test/fixtures/go/single-import-declarations.parseA.txt @@ -4,9 +4,9 @@ (QualifiedImport (Identifier) (Identifier)) - (WildcardImport + (Import (Identifier) - (Identifier)) + (TextElement)) (QualifiedImport (Identifier) (Identifier)) diff --git a/test/fixtures/go/single-import-declarations.parseB.txt b/test/fixtures/go/single-import-declarations.parseB.txt index 2d35be6ef..65e7cdd40 100644 --- a/test/fixtures/go/single-import-declarations.parseB.txt +++ b/test/fixtures/go/single-import-declarations.parseB.txt @@ -4,9 +4,9 @@ (QualifiedImport (Identifier) (Identifier)) - (WildcardImport + (Import (Identifier) - (Identifier)) + (TextElement)) (QualifiedImport (Identifier) (Identifier)) diff --git a/test/fixtures/javascript/import.diffA-B.txt b/test/fixtures/javascript/import.diffA-B.txt index 48e18361f..16e534292 100644 --- a/test/fixtures/javascript/import.diffA-B.txt +++ b/test/fixtures/javascript/import.diffA-B.txt @@ -1,41 +1,65 @@ (Program - (Import - { (Identifier) - ->(Identifier) }) - (QualifiedImport - { (Identifier) - ->(Identifier) } - { (Identifier) - ->(Identifier) }) -{ (Import - {-(Identifier)-}) -->(Import - {+(Identifier)+}) } -{ (Import - {-(Identifier)-}) -->(Import - {+(Identifier)+}) } -{ (Import - {-(Identifier)-}) -->(Import - {+(Identifier)+}) } - ( - (Import - { (Identifier) - ->(Identifier) }) - { (Import - {-(Identifier)-}) - ->(Import - {+(Identifier)+}) }) - ( - (Import - { (Identifier) - ->(Identifier) }) - (QualifiedImport - { (Identifier) - ->(Identifier) } - { (Identifier) - ->(Identifier) })) - (Import - { (Identifier) - ->(Identifier) })) +{+(Import + {+(Identifier)+} + {+(Empty)+})+} +{+(QualifiedImport + {+(Identifier)+} + {+(Identifier)+})+} +{+(Import + {+(Identifier)+} + {+(Empty)+})+} +{+(Import + {+(Identifier)+} + {+(Empty)+})+} +{+(Import + {+(Identifier)+} + {+(Empty)+})+} +{+( + {+(Import + {+(Identifier)+} + {+(Empty)+})+} + {+(Import + {+(Identifier)+} + {+(Empty)+})+})+} +{+( + {+(Import + {+(Identifier)+} + {+(Empty)+})+} + {+(QualifiedImport + {+(Identifier)+} + {+(Identifier)+})+})+} +{+(SideEffectImport + {+(Identifier)+} + {+(Empty)+})+} +{-(Import + {-(Identifier)-} + {-(Empty)-})-} +{-(QualifiedImport + {-(Identifier)-} + {-(Identifier)-})-} +{-(Import + {-(Identifier)-} + {-(Empty)-})-} +{-(Import + {-(Identifier)-} + {-(Empty)-})-} +{-(Import + {-(Identifier)-} + {-(Empty)-})-} +{-( + {-(Import + {-(Identifier)-} + {-(Empty)-})-} + {-(Import + {-(Identifier)-} + {-(Empty)-})-})-} +{-( + {-(Import + {-(Identifier)-} + {-(Empty)-})-} + {-(QualifiedImport + {-(Identifier)-} + {-(Identifier)-})-})-} +{-(SideEffectImport + {-(Identifier)-} + {-(Empty)-})-}) diff --git a/test/fixtures/javascript/import.diffB-A.txt b/test/fixtures/javascript/import.diffB-A.txt index 48e18361f..c5d3c09bb 100644 --- a/test/fixtures/javascript/import.diffB-A.txt +++ b/test/fixtures/javascript/import.diffB-A.txt @@ -1,41 +1,65 @@ (Program - (Import - { (Identifier) - ->(Identifier) }) - (QualifiedImport - { (Identifier) - ->(Identifier) } - { (Identifier) - ->(Identifier) }) +{+(Import + {+(Identifier)+} + {+(Empty)+})+} +{+(QualifiedImport + {+(Identifier)+} + {+(Identifier)+})+} { (Import - {-(Identifier)-}) + {-(Identifier)-} + {-(Empty)-}) ->(Import - {+(Identifier)+}) } -{ (Import - {-(Identifier)-}) -->(Import - {+(Identifier)+}) } -{ (Import - {-(Identifier)-}) -->(Import - {+(Identifier)+}) } - ( - (Import - { (Identifier) - ->(Identifier) }) - { (Import - {-(Identifier)-}) - ->(Import - {+(Identifier)+}) }) - ( - (Import - { (Identifier) - ->(Identifier) }) - (QualifiedImport - { (Identifier) - ->(Identifier) } - { (Identifier) - ->(Identifier) })) - (Import - { (Identifier) - ->(Identifier) })) + {+(Identifier)+} + {+(Empty)+}) } +{+(Import + {+(Identifier)+} + {+(Empty)+})+} +{+(Import + {+(Identifier)+} + {+(Empty)+})+} +{+( + {+(Import + {+(Identifier)+} + {+(Empty)+})+} + {+(Import + {+(Identifier)+} + {+(Empty)+})+})+} +{+( + {+(Import + {+(Identifier)+} + {+(Empty)+})+} + {+(QualifiedImport + {+(Identifier)+} + {+(Identifier)+})+})+} +{+(SideEffectImport + {+(Identifier)+} + {+(Empty)+})+} +{-(QualifiedImport + {-(Identifier)-} + {-(Identifier)-})-} +{-(Import + {-(Identifier)-} + {-(Empty)-})-} +{-(Import + {-(Identifier)-} + {-(Empty)-})-} +{-(Import + {-(Identifier)-} + {-(Empty)-})-} +{-( + {-(Import + {-(Identifier)-} + {-(Empty)-})-} + {-(Import + {-(Identifier)-} + {-(Empty)-})-})-} +{-( + {-(Import + {-(Identifier)-} + {-(Empty)-})-} + {-(QualifiedImport + {-(Identifier)-} + {-(Identifier)-})-})-} +{-(SideEffectImport + {-(Identifier)-} + {-(Empty)-})-}) diff --git a/test/fixtures/javascript/import.parseA.txt b/test/fixtures/javascript/import.parseA.txt index 6bd6755f3..ea05f8535 100644 --- a/test/fixtures/javascript/import.parseA.txt +++ b/test/fixtures/javascript/import.parseA.txt @@ -1,25 +1,33 @@ (Program (Import - (Identifier)) + (Identifier) + (Empty)) (QualifiedImport (Identifier) (Identifier)) (Import - (Identifier)) + (Identifier) + (Empty)) (Import - (Identifier)) + (Identifier) + (Empty)) (Import - (Identifier)) + (Identifier) + (Empty)) ( (Import - (Identifier)) + (Identifier) + (Empty)) (Import - (Identifier))) + (Identifier) + (Empty))) ( (Import - (Identifier)) + (Identifier) + (Empty)) (QualifiedImport (Identifier) (Identifier))) - (Import - (Identifier))) + (SideEffectImport + (Identifier) + (Empty))) diff --git a/test/fixtures/javascript/import.parseB.txt b/test/fixtures/javascript/import.parseB.txt index 6bd6755f3..ea05f8535 100644 --- a/test/fixtures/javascript/import.parseB.txt +++ b/test/fixtures/javascript/import.parseB.txt @@ -1,25 +1,33 @@ (Program (Import - (Identifier)) + (Identifier) + (Empty)) (QualifiedImport (Identifier) (Identifier)) (Import - (Identifier)) + (Identifier) + (Empty)) (Import - (Identifier)) + (Identifier) + (Empty)) (Import - (Identifier)) + (Identifier) + (Empty)) ( (Import - (Identifier)) + (Identifier) + (Empty)) (Import - (Identifier))) + (Identifier) + (Empty))) ( (Import - (Identifier)) + (Identifier) + (Empty)) (QualifiedImport (Identifier) (Identifier))) - (Import - (Identifier))) + (SideEffectImport + (Identifier) + (Empty))) diff --git a/test/fixtures/python/import-from-statement.diffA-B.txt b/test/fixtures/python/import-from-statement.diffA-B.txt index 0d182be1a..5aa00923e 100644 --- a/test/fixtures/python/import-from-statement.diffA-B.txt +++ b/test/fixtures/python/import-from-statement.diffA-B.txt @@ -1,34 +1,34 @@ (Program -{+(QualifiedImport +{+(Import {+(Identifier)+} {+(Empty)+})+} -{+(QualifiedImport +{+(Import {+(Identifier)+} {+(Empty)+})+} -{+(WildcardImport +{ (Import + {-(Identifier)-} + {-(Empty)-}) +->(Import {+(Identifier)+} - {+(Identifier)+})+} -{+(QualifiedImport + {+(Empty)+}) } +{+(Import {+(Identifier)+} {+(Empty)+})+} -{+(QualifiedImport +{+(Import {+(Identifier)+} {+(Empty)+})+} -{+(QualifiedImport +{ (Import + {-(Identifier)-} + {-(Empty)-}) +->(Import {+(Empty)+} - {+(Empty)+})+} -{-(QualifiedImport + {+(Empty)+}) } +{-(Import {-(Identifier)-} {-(Empty)-})-} -{-(QualifiedImport +{-(Import {-(Identifier)-} {-(Empty)-})-} -{-(WildcardImport - {-(Identifier)-} - {-(Identifier)-})-} -{-(QualifiedImport - {-(Identifier)-} - {-(Empty)-})-} -{-(QualifiedImport +{-(Import {-(Empty)-} {-(Empty)-})-}) diff --git a/test/fixtures/python/import-from-statement.diffB-A.txt b/test/fixtures/python/import-from-statement.diffB-A.txt index 2571e0d5f..53a5a0e48 100644 --- a/test/fixtures/python/import-from-statement.diffB-A.txt +++ b/test/fixtures/python/import-from-statement.diffB-A.txt @@ -1,34 +1,34 @@ (Program -{+(QualifiedImport +{+(Import {+(Identifier)+} {+(Empty)+})+} -{+(QualifiedImport +{+(Import {+(Identifier)+} {+(Empty)+})+} -{+(WildcardImport - {+(Identifier)+} - {+(Identifier)+})+} -{+(QualifiedImport +{+(Import {+(Identifier)+} {+(Empty)+})+} -{+(QualifiedImport +{+(Import + {+(Identifier)+} + {+(Empty)+})+} +{+(Import {+(Empty)+} {+(Empty)+})+} -{-(QualifiedImport +{-(Import {-(Identifier)-} {-(Empty)-})-} -{-(QualifiedImport +{-(Import {-(Identifier)-} {-(Empty)-})-} -{-(WildcardImport - {-(Identifier)-} - {-(Identifier)-})-} -{-(QualifiedImport +{-(Import {-(Identifier)-} {-(Empty)-})-} -{-(QualifiedImport +{-(Import {-(Identifier)-} {-(Empty)-})-} -{-(QualifiedImport +{-(Import + {-(Identifier)-} + {-(Empty)-})-} +{-(Import {-(Empty)-} {-(Empty)-})-}) diff --git a/test/fixtures/python/import-from-statement.parseA.txt b/test/fixtures/python/import-from-statement.parseA.txt index 8897d83a5..06ddf01b0 100644 --- a/test/fixtures/python/import-from-statement.parseA.txt +++ b/test/fixtures/python/import-from-statement.parseA.txt @@ -1,16 +1,16 @@ (Program - (QualifiedImport + (Import (Identifier) (Empty)) - (QualifiedImport + (Import (Identifier) (Empty)) - (WildcardImport - (Identifier) - (Identifier)) - (QualifiedImport + (Import (Identifier) (Empty)) - (QualifiedImport + (Import + (Identifier) + (Empty)) + (Import (Empty) (Empty))) diff --git a/test/fixtures/python/import-from-statement.parseB.txt b/test/fixtures/python/import-from-statement.parseB.txt index d409e0e2d..9b3febadc 100644 --- a/test/fixtures/python/import-from-statement.parseB.txt +++ b/test/fixtures/python/import-from-statement.parseB.txt @@ -1,19 +1,19 @@ (Program - (QualifiedImport + (Import (Identifier) (Empty)) - (QualifiedImport + (Import (Identifier) (Empty)) - (WildcardImport - (Identifier) - (Identifier)) - (QualifiedImport + (Import (Identifier) (Empty)) - (QualifiedImport + (Import (Identifier) (Empty)) - (QualifiedImport + (Import + (Identifier) + (Empty)) + (Import (Empty) (Empty))) diff --git a/test/fixtures/ruby/require.diffA-B.txt b/test/fixtures/ruby/require.diffA-B.txt index 45b1b787c..000634991 100644 --- a/test/fixtures/ruby/require.diffA-B.txt +++ b/test/fixtures/ruby/require.diffA-B.txt @@ -1,11 +1,13 @@ (Program (Import { (TextElement) - ->(TextElement) }) + ->(TextElement) } + (Empty)) {+(Import {+( {+(Symbol)+} - {+(TextElement)+})+})+} + {+(TextElement)+})+} + {+(Empty)+})+} {-(Call {-(Identifier)-} {-(Identifier)-} diff --git a/test/fixtures/ruby/require.diffB-A.txt b/test/fixtures/ruby/require.diffB-A.txt index ff66aea10..7ea78ad11 100644 --- a/test/fixtures/ruby/require.diffB-A.txt +++ b/test/fixtures/ruby/require.diffB-A.txt @@ -1,7 +1,8 @@ (Program (Import { (TextElement) - ->(TextElement) }) + ->(TextElement) } + (Empty)) {+(Call {+(Identifier)+} {+(Identifier)+} @@ -9,4 +10,5 @@ {-(Import {-( {-(Symbol)-} - {-(TextElement)-})-})-}) + {-(TextElement)-})-} + {-(Empty)-})-}) diff --git a/test/fixtures/ruby/require.parseA.txt b/test/fixtures/ruby/require.parseA.txt index c566f858c..e4960dafe 100644 --- a/test/fixtures/ruby/require.parseA.txt +++ b/test/fixtures/ruby/require.parseA.txt @@ -1,6 +1,7 @@ (Program (Import - (TextElement)) + (TextElement) + (Empty)) (Call (Identifier) (Identifier) diff --git a/test/fixtures/ruby/require.parseB.txt b/test/fixtures/ruby/require.parseB.txt index 48a0e219a..1203a16f7 100644 --- a/test/fixtures/ruby/require.parseB.txt +++ b/test/fixtures/ruby/require.parseB.txt @@ -1,7 +1,9 @@ (Program (Import - (TextElement)) + (TextElement) + (Empty)) (Import ( (Symbol) - (TextElement)))) + (TextElement)) + (Empty))) diff --git a/test/fixtures/typescript/ambient-exports.diffA-B.txt b/test/fixtures/typescript/ambient-exports.diffA-B.txt index a7c5bad9f..fda31a882 100644 --- a/test/fixtures/typescript/ambient-exports.diffA-B.txt +++ b/test/fixtures/typescript/ambient-exports.diffA-B.txt @@ -1,24 +1,30 @@ (Program (DefaultExport - {(Class - {-(Identifier)-} - {-([])-}) - -> - (Function + { (Class + {-(Identifier)-} + {-([])-}) + ->(Function + {+(Empty)+} + {+(Empty)+} + {+(Identifier)+} + {+(RequiredParameter {+(Empty)+} {+(Empty)+} - {+(Identifier)+} - {+(RequiredParameter - {+(Empty)+} - {+(Empty)+} - {+(Annotation{+(PredefinedType)+})+} - {+(Assignment{+(Identifier)+}{+(Empty)+})+})+} - {+(RequiredParameter - {+(Empty)+} - {+(Empty)+} - {+(Annotation{+(PredefinedType)+})+} - {+(Assignment{+(Identifier)+}{+(Empty)+})+})+} - {+({+(Return + {+(Annotation + {+(PredefinedType)+})+} + {+(Assignment + {+(Identifier)+} + {+(Empty)+})+})+} + {+(RequiredParameter + {+(Empty)+} + {+(Empty)+} + {+(Annotation + {+(PredefinedType)+})+} + {+(Assignment + {+(Identifier)+} + {+(Empty)+})+})+} + {+( + {+(Return {+(Hash {+(ShorthandPropertyIdentifier)+} - {+(ShorthandPropertyIdentifier)+})+})+})+})})) \ No newline at end of file + {+(ShorthandPropertyIdentifier)+})+})+})+}) })) diff --git a/test/fixtures/typescript/ambient-exports.diffB-A.txt b/test/fixtures/typescript/ambient-exports.diffB-A.txt index 22561ea57..afbaad04f 100644 --- a/test/fixtures/typescript/ambient-exports.diffB-A.txt +++ b/test/fixtures/typescript/ambient-exports.diffB-A.txt @@ -1,10 +1,30 @@ (Program (DefaultExport - {(Function + { (Function + {-(Empty)-} + {-(Empty)-} + {-(Identifier)-} + {-(RequiredParameter {-(Empty)-} {-(Empty)-} - {-(Identifier)-} - {-(RequiredParameter{-(Empty)-}{-(Empty)-}{-(Annotation{-(PredefinedType)-})-}{-(Assignment{-(Identifier)-}{-(Empty)-})-})-} - {-(RequiredParameter{-(Empty)-}{-(Empty)-}{-(Annotation{-(PredefinedType)-})-}{-(Assignment{-(Identifier)-}{-(Empty)-})-})-} - {-({-(Return{-(Hash{-(ShorthandPropertyIdentifier)-}{-(ShorthandPropertyIdentifier)-})-})-})-}) - ->(Class{+(Identifier)+}{+([])+})})) \ No newline at end of file + {-(Annotation + {-(PredefinedType)-})-} + {-(Assignment + {-(Identifier)-} + {-(Empty)-})-})-} + {-(RequiredParameter + {-(Empty)-} + {-(Empty)-} + {-(Annotation + {-(PredefinedType)-})-} + {-(Assignment + {-(Identifier)-} + {-(Empty)-})-})-} + {-( + {-(Return + {-(Hash + {-(ShorthandPropertyIdentifier)-} + {-(ShorthandPropertyIdentifier)-})-})-})-}) + ->(Class + {+(Identifier)+} + {+([])+}) })) diff --git a/test/fixtures/typescript/export.diffA-B.txt b/test/fixtures/typescript/export.diffA-B.txt index eb927dac7..a046f61e7 100644 --- a/test/fixtures/typescript/export.diffA-B.txt +++ b/test/fixtures/typescript/export.diffA-B.txt @@ -1,37 +1,81 @@ (Program - {(QualifiedExport)->(QualifiedExport)} - {(QualifiedExport)->(QualifiedExport)} +{ (QualifiedExport) +->(QualifiedExport) } +{ (QualifiedExport) +->(QualifiedExport) } (DefaultExport (VariableDeclaration - (Assignment(Empty){(Identifier)->(Identifier)}(Empty)) - (Assignment(Empty){(Identifier)->(Identifier)}(Empty)) - (Assignment(Empty){(Identifier)->(Identifier)}(Empty)))) + (Assignment + (Empty) + { (Identifier) + ->(Identifier) } + (Empty)) + (Assignment + (Empty) + { (Identifier) + ->(Identifier) } + (Empty)) + (Assignment + (Empty) + { (Identifier) + ->(Identifier) } + (Empty)))) (DefaultExport (VariableDeclaration - {-(Assignment{-(Empty)-}{-(Identifier)-}{-(Identifier)-})-} - (Assignment(Empty)(Identifier)(Identifier)) - (Assignment(Empty)(Identifier){(Empty)->(Identifier)}) - {+(Assignment{+(Empty)+}{+(Identifier)+}{+(Empty)+})+} - (Assignment(Empty){(Identifier)->(Identifier)}(Empty)))) + {-(Assignment + {-(Empty)-} + {-(Identifier)-} + {-(Identifier)-})-} + (Assignment + (Empty) + (Identifier) + (Identifier)) + (Assignment + (Empty) + (Identifier) + { (Empty) + ->(Identifier) }) + {+(Assignment + {+(Empty)+} + {+(Identifier)+} + {+(Empty)+})+} + (Assignment + (Empty) + { (Identifier) + ->(Identifier) } + (Empty)))) (DefaultExport - {(Identifier)->(Identifier)}) - {+(DefaultExport - {+(Function{+(Empty)+}{+(Empty)+}{+(Identifier)+}{+([])+})+})+} + { (Identifier) + ->(Identifier) }) +{+(DefaultExport + {+(Function + {+(Empty)+} + {+(Empty)+} + {+(Identifier)+} + {+([])+})+})+} (DefaultExport - (Function(Empty)(Empty)(Empty)([]))) - {+(QualifiedExport)+} - {+(DefaultExport - {+(TextElement)+})+} - {+(QualifiedExportFrom - {+(Identifier)+})+} - {+(QualifiedExportFrom - {+(Identifier)+})+} - {-(DefaultExport - {-(Function{-(Empty)-}{-(Empty)-}{-(Identifier)-}{-([])-})-})-} - {-(QualifiedExport)-} - {-(DefaultExport - {-(TextElement)-})-} - {-(QualifiedExportFrom - {-(Identifier)-})-} - {-(QualifiedExportFrom - {-(Identifier)-})-}) \ No newline at end of file + (Function + (Empty) + (Empty) + (Empty) + ([]))) +{+(QualifiedExport)+} +{+(DefaultExport + {+(TextElement)+})+} +{+(QualifiedExportFrom + {+(Identifier)+})+} +{+(QualifiedExportFrom + {+(Identifier)+})+} +{-(DefaultExport + {-(Function + {-(Empty)-} + {-(Empty)-} + {-(Identifier)-} + {-([])-})-})-} +{-(QualifiedExport)-} +{-(DefaultExport + {-(TextElement)-})-} +{-(QualifiedExportFrom + {-(Identifier)-})-} +{-(QualifiedExportFrom + {-(Identifier)-})-}) diff --git a/test/fixtures/typescript/export.diffB-A.txt b/test/fixtures/typescript/export.diffB-A.txt index 03d231796..6b97b37d8 100644 --- a/test/fixtures/typescript/export.diffB-A.txt +++ b/test/fixtures/typescript/export.diffB-A.txt @@ -1,29 +1,83 @@ (Program - {(QualifiedExport)->(QualifiedExport)} - {(QualifiedExport)->(QualifiedExport)} +{ (QualifiedExport) +->(QualifiedExport) } +{ (QualifiedExport) +->(QualifiedExport) } (DefaultExport (VariableDeclaration - (Assignment(Empty){(Identifier)->(Identifier)}(Empty)) - (Assignment(Empty){(Identifier)->(Identifier)}(Empty)) - (Assignment(Empty){(Identifier)->(Identifier)}(Empty)))) + (Assignment + (Empty) + { (Identifier) + ->(Identifier) } + (Empty)) + (Assignment + (Empty) + { (Identifier) + ->(Identifier) } + (Empty)) + (Assignment + (Empty) + { (Identifier) + ->(Identifier) } + (Empty)))) (DefaultExport (VariableDeclaration - {+(Assignment{+(Empty)+}{+(Identifier)+}{+(Identifier)+})+} - (Assignment(Empty)(Identifier)(Identifier)) - (Assignment(Empty)(Identifier){(Identifier)->(Empty)}) - {+(Assignment{+(Empty)+}{+(Identifier)+}{+(Empty)+})+} - {-(Assignment{-(Empty)-}{-(Identifier)-}{-(Empty)-})-} - {-(Assignment{-(Empty)-}{-(Identifier)-}{-(Empty)-})-})) + {+(Assignment + {+(Empty)+} + {+(Identifier)+} + {+(Identifier)+})+} + (Assignment + (Empty) + (Identifier) + (Identifier)) + (Assignment + (Empty) + (Identifier) + { (Identifier) + ->(Empty) }) + {+(Assignment + {+(Empty)+} + {+(Identifier)+} + {+(Empty)+})+} + {-(Assignment + {-(Empty)-} + {-(Identifier)-} + {-(Empty)-})-} + {-(Assignment + {-(Empty)-} + {-(Identifier)-} + {-(Empty)-})-})) (DefaultExport - {(Identifier)->(Identifier)}) - {-(DefaultExport - {-(Function{-(Empty)-}{-(Empty)-}{-(Identifier)-}{-([])-})-})-} + { (Identifier) + ->(Identifier) }) +{-(DefaultExport + {-(Function + {-(Empty)-} + {-(Empty)-} + {-(Identifier)-} + {-([])-})-})-} (DefaultExport - (Function(Empty)(Empty)(Empty)([]))) - {+(DefaultExport - {+(Function{+(Empty)+}{+(Empty)+}{+(Identifier)+}{+([])+})+})+} - {(QualifiedExport)->(QualifiedExport)} + (Function + (Empty) + (Empty) + (Empty) + ([]))) +{+(DefaultExport + {+(Function + {+(Empty)+} + {+(Empty)+} + {+(Identifier)+} + {+([])+})+})+} +{ (QualifiedExport) +->(QualifiedExport) } (DefaultExport - {(TextElement)->(TextElement)}) - {(QualifiedExportFrom {-(Identifier)-})->(QualifiedExportFrom {+(Identifier)+})} - {(QualifiedExportFrom {-(Identifier)-})->(QualifiedExportFrom {+(Identifier)+})}) \ No newline at end of file + { (TextElement) + ->(TextElement) }) +{ (QualifiedExportFrom + {-(Identifier)-}) +->(QualifiedExportFrom + {+(Identifier)+}) } +{ (QualifiedExportFrom + {-(Identifier)-}) +->(QualifiedExportFrom + {+(Identifier)+}) }) diff --git a/test/fixtures/typescript/export.parseA.txt b/test/fixtures/typescript/export.parseA.txt index 2c57afc28..fa9cfc3dd 100644 --- a/test/fixtures/typescript/export.parseA.txt +++ b/test/fixtures/typescript/export.parseA.txt @@ -3,19 +3,54 @@ (QualifiedExport) (DefaultExport (VariableDeclaration - (Assignment(Empty)(Identifier)(Empty)) - (Assignment(Empty)(Identifier)(Empty)) - (Assignment(Empty)(Identifier)(Empty)))) + (Assignment + (Empty) + (Identifier) + (Empty)) + (Assignment + (Empty) + (Identifier) + (Empty)) + (Assignment + (Empty) + (Identifier) + (Empty)))) (DefaultExport (VariableDeclaration - (Assignment(Empty)(Identifier)(Identifier)) - (Assignment(Empty)(Identifier)(Identifier)) - (Assignment(Empty)(Identifier)(Empty)) - (Assignment(Empty)(Identifier)(Empty)))) - (DefaultExport (Identifier)) - (DefaultExport (Function(Empty)(Empty)(Empty)([]))) - (DefaultExport (Function(Empty)(Empty)(Identifier)([]))) + (Assignment + (Empty) + (Identifier) + (Identifier)) + (Assignment + (Empty) + (Identifier) + (Identifier)) + (Assignment + (Empty) + (Identifier) + (Empty)) + (Assignment + (Empty) + (Identifier) + (Empty)))) + (DefaultExport + (Identifier)) + (DefaultExport + (Function + (Empty) + (Empty) + (Empty) + ([]))) + (DefaultExport + (Function + (Empty) + (Empty) + (Identifier) + ([]))) (QualifiedExport) - (DefaultExport (TextElement)) - (QualifiedExportFrom (Identifier)) - (QualifiedExportFrom (Identifier))) \ No newline at end of file + (DefaultExport + (TextElement)) + (QualifiedExportFrom + (Identifier)) + (QualifiedExportFrom + (Identifier))) diff --git a/test/fixtures/typescript/export.parseB.txt b/test/fixtures/typescript/export.parseB.txt index 217fc2584..bf96a822f 100644 --- a/test/fixtures/typescript/export.parseB.txt +++ b/test/fixtures/typescript/export.parseB.txt @@ -3,19 +3,54 @@ (QualifiedExport) (DefaultExport (VariableDeclaration - (Assignment(Empty)(Identifier)(Empty)) - (Assignment(Empty)(Identifier)(Empty)) - (Assignment(Empty)(Identifier)(Empty)))) + (Assignment + (Empty) + (Identifier) + (Empty)) + (Assignment + (Empty) + (Identifier) + (Empty)) + (Assignment + (Empty) + (Identifier) + (Empty)))) (DefaultExport (VariableDeclaration - (Assignment(Empty)(Identifier)(Identifier)) - (Assignment(Empty)(Identifier)(Identifier)) - (Assignment(Empty)(Identifier)(Empty)) - (Assignment(Empty)(Identifier)(Empty)))) - (DefaultExport(Identifier)) - (DefaultExport(Function(Empty)(Empty)(Identifier)([]))) - (DefaultExport(Function(Empty)(Empty)(Empty)([]))) + (Assignment + (Empty) + (Identifier) + (Identifier)) + (Assignment + (Empty) + (Identifier) + (Identifier)) + (Assignment + (Empty) + (Identifier) + (Empty)) + (Assignment + (Empty) + (Identifier) + (Empty)))) + (DefaultExport + (Identifier)) + (DefaultExport + (Function + (Empty) + (Empty) + (Identifier) + ([]))) + (DefaultExport + (Function + (Empty) + (Empty) + (Empty) + ([]))) (QualifiedExport) - (DefaultExport (TextElement)) - (QualifiedExportFrom (Identifier)) - (QualifiedExportFrom (Identifier))) \ No newline at end of file + (DefaultExport + (TextElement)) + (QualifiedExportFrom + (Identifier)) + (QualifiedExportFrom + (Identifier))) diff --git a/test/fixtures/typescript/import.diffA-B.txt b/test/fixtures/typescript/import.diffA-B.txt index 987fcaecf..70af8adff 100644 --- a/test/fixtures/typescript/import.diffA-B.txt +++ b/test/fixtures/typescript/import.diffA-B.txt @@ -1,45 +1,68 @@ (Program - (Import - { (Identifier) - ->(Identifier) }) - (QualifiedImport - { (Identifier) - ->(Identifier) } - { (Identifier) - ->(Identifier) }) -{ (Import - {-(Identifier)-}) -->(Import - {+(Identifier)+}) } -{ (Import - {-(Identifier)-}) -->(Import - {+(Identifier)+}) } -{ (Import - {-(Identifier)-}) -->(Import - {+(Identifier)+}) } - ( - (Import - { (Identifier) - ->(Identifier) }) - { (Import - {-(Identifier)-}) - ->(Import - {+(Identifier)+}) }) - ( - (Import - { (Identifier) - ->(Identifier) }) - (QualifiedImport - { (Identifier) - ->(Identifier) } - { (Identifier) - ->(Identifier) })) {+(Import + {+(Identifier)+} + {+(Empty)+})+} +{+(QualifiedImport + {+(Identifier)+} {+(Identifier)+})+} +{+(Import + {+(Identifier)+} + {+(Empty)+})+} +{+(Import + {+(Identifier)+} + {+(Empty)+})+} +{+(Import + {+(Identifier)+} + {+(Empty)+})+} +{+( + {+(Import + {+(Identifier)+} + {+(Empty)+})+} + {+(Import + {+(Identifier)+} + {+(Empty)+})+})+} +{+( + {+(Import + {+(Identifier)+} + {+(Empty)+})+} + {+(QualifiedImport + {+(Identifier)+} + {+(Identifier)+})+})+} +{+(SideEffectImport + {+(Identifier)+} + {+(Empty)+})+} {-(Import + {-(Identifier)-} + {-(Empty)-})-} +{-(QualifiedImport + {-(Identifier)-} {-(Identifier)-})-} +{-(Import + {-(Identifier)-} + {-(Empty)-})-} +{-(Import + {-(Identifier)-} + {-(Empty)-})-} +{-(Import + {-(Identifier)-} + {-(Empty)-})-} +{-( + {-(Import + {-(Identifier)-} + {-(Empty)-})-} + {-(Import + {-(Identifier)-} + {-(Empty)-})-})-} +{-( + {-(Import + {-(Identifier)-} + {-(Empty)-})-} + {-(QualifiedImport + {-(Identifier)-} + {-(Identifier)-})-})-} +{-(SideEffectImport + {-(Identifier)-} + {-(Empty)-})-} {-(QualifiedImport {-(Identifier)-} {-(Identifier)-})-}) diff --git a/test/fixtures/typescript/import.diffB-A.txt b/test/fixtures/typescript/import.diffB-A.txt index 79a3b8e36..17359b2a2 100644 --- a/test/fixtures/typescript/import.diffB-A.txt +++ b/test/fixtures/typescript/import.diffB-A.txt @@ -1,45 +1,68 @@ (Program - (Import - { (Identifier) - ->(Identifier) }) - (QualifiedImport - { (Identifier) - ->(Identifier) } - { (Identifier) - ->(Identifier) }) -{ (Import - {-(Identifier)-}) -->(Import - {+(Identifier)+}) } -{ (Import - {-(Identifier)-}) -->(Import - {+(Identifier)+}) } -{ (Import - {-(Identifier)-}) -->(Import - {+(Identifier)+}) } - ( - (Import - { (Identifier) - ->(Identifier) }) - { (Import - {-(Identifier)-}) - ->(Import - {+(Identifier)+}) }) - ( - (Import - { (Identifier) - ->(Identifier) }) - (QualifiedImport - { (Identifier) - ->(Identifier) } - { (Identifier) - ->(Identifier) })) {+(Import - {+(Identifier)+})+} + {+(Identifier)+} + {+(Empty)+})+} {+(QualifiedImport {+(Identifier)+} {+(Identifier)+})+} +{ (Import + {-(Identifier)-} + {-(Empty)-}) +->(Import + {+(Identifier)+} + {+(Empty)+}) } +{+(Import + {+(Identifier)+} + {+(Empty)+})+} +{+(Import + {+(Identifier)+} + {+(Empty)+})+} +{+( + {+(Import + {+(Identifier)+} + {+(Empty)+})+} + {+(Import + {+(Identifier)+} + {+(Empty)+})+})+} +{+( + {+(Import + {+(Identifier)+} + {+(Empty)+})+} + {+(QualifiedImport + {+(Identifier)+} + {+(Identifier)+})+})+} +{+(SideEffectImport + {+(Identifier)+} + {+(Empty)+})+} +{+(QualifiedImport + {+(Identifier)+} + {+(Identifier)+})+} +{-(QualifiedImport + {-(Identifier)-} + {-(Identifier)-})-} {-(Import - {-(Identifier)-})-}) + {-(Identifier)-} + {-(Empty)-})-} +{-(Import + {-(Identifier)-} + {-(Empty)-})-} +{-(Import + {-(Identifier)-} + {-(Empty)-})-} +{-( + {-(Import + {-(Identifier)-} + {-(Empty)-})-} + {-(Import + {-(Identifier)-} + {-(Empty)-})-})-} +{-( + {-(Import + {-(Identifier)-} + {-(Empty)-})-} + {-(QualifiedImport + {-(Identifier)-} + {-(Identifier)-})-})-} +{-(SideEffectImport + {-(Identifier)-} + {-(Empty)-})-}) diff --git a/test/fixtures/typescript/import.parseA.txt b/test/fixtures/typescript/import.parseA.txt index 23ec906e5..b51ad3f68 100644 --- a/test/fixtures/typescript/import.parseA.txt +++ b/test/fixtures/typescript/import.parseA.txt @@ -1,28 +1,36 @@ (Program (Import - (Identifier)) + (Identifier) + (Empty)) (QualifiedImport (Identifier) (Identifier)) (Import - (Identifier)) + (Identifier) + (Empty)) (Import - (Identifier)) + (Identifier) + (Empty)) (Import - (Identifier)) + (Identifier) + (Empty)) ( (Import - (Identifier)) + (Identifier) + (Empty)) (Import - (Identifier))) + (Identifier) + (Empty))) ( (Import - (Identifier)) + (Identifier) + (Empty)) (QualifiedImport (Identifier) (Identifier))) - (Import - (Identifier)) + (SideEffectImport + (Identifier) + (Empty)) (QualifiedImport (Identifier) (Identifier))) diff --git a/test/fixtures/typescript/import.parseB.txt b/test/fixtures/typescript/import.parseB.txt index 6bd6755f3..ea05f8535 100644 --- a/test/fixtures/typescript/import.parseB.txt +++ b/test/fixtures/typescript/import.parseB.txt @@ -1,25 +1,33 @@ (Program (Import - (Identifier)) + (Identifier) + (Empty)) (QualifiedImport (Identifier) (Identifier)) (Import - (Identifier)) + (Identifier) + (Empty)) (Import - (Identifier)) + (Identifier) + (Empty)) (Import - (Identifier)) + (Identifier) + (Empty)) ( (Import - (Identifier)) + (Identifier) + (Empty)) (Import - (Identifier))) + (Identifier) + (Empty))) ( (Import - (Identifier)) + (Identifier) + (Empty)) (QualifiedImport (Identifier) (Identifier))) - (Import - (Identifier))) + (SideEffectImport + (Identifier) + (Empty))) From 87543edb9bb30bc7646cda991233f56eecb27909 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Wed, 14 Mar 2018 16:29:58 -0700 Subject: [PATCH 05/30] Whoops... should not have removed this --- src/Data/Abstract/Environment.hs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Data/Abstract/Environment.hs b/src/Data/Abstract/Environment.hs index 9ef90be4a..99c8a5064 100644 --- a/src/Data/Abstract/Environment.hs +++ b/src/Data/Abstract/Environment.hs @@ -7,6 +7,7 @@ import Data.Abstract.Live import Data.Semigroup.Reducer import Prologue import qualified Data.Map as Map +import qualified Data.Set as Set -- | A map of names to addresses that represents the evaluation environment. newtype Environment l a = Environment { unEnvironment :: Map.Map Name (Address l a) } @@ -42,6 +43,9 @@ exportInsert name value = Exports . Map.insert name value . unExports envRoots :: (Ord l, Foldable t) => Environment l a -> t Name -> Live l a envRoots env = foldr ((<>) . maybe mempty liveSingleton . flip envLookup env) mempty +envAll :: (Ord l) => Environment l a -> Live l a +envAll (Environment env) = Live $ Set.fromList (Map.elems env) + -- Instances From 7351b0130485a2af78a84c0eba6ed44e266253e8 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Thu, 15 Mar 2018 08:57:08 -0700 Subject: [PATCH 06/30] modifyExports and curry addExports --- src/Control/Abstract/Evaluator.hs | 12 +++++++----- src/Data/Syntax/Declaration.hs | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Control/Abstract/Evaluator.hs b/src/Control/Abstract/Evaluator.hs index 1fd23bd31..9dcc9c58c 100644 --- a/src/Control/Abstract/Evaluator.hs +++ b/src/Control/Abstract/Evaluator.hs @@ -58,17 +58,19 @@ class Monad m => MonadEnvironment value m | m -> value where localEnv :: (EnvironmentFor value -> EnvironmentFor value) -> m a -> m a -- | Update the global environment. -modifyGlobalEnv :: MonadEnvironment value m => (EnvironmentFor value -> EnvironmentFor value) -> m () +modifyGlobalEnv :: MonadEnvironment value m => (EnvironmentFor value -> EnvironmentFor value) -> m () modifyGlobalEnv f = do env <- getGlobalEnv putGlobalEnv $! f env --- | Add an export to the global export state. -addExport :: MonadEvaluator term value m => Name -> (Name, Maybe (Address (LocationFor value) value)) -> m () -addExport name value = do +modifyExports :: MonadEnvironment value m => (ExportsFor value -> ExportsFor value) -> m () +modifyExports f = do exports <- getExports - putExports $! exportInsert name value exports + putExports $! f exports +-- | Add an export to the global export state. +addExport :: MonadEnvironment value m => Name -> Name -> Maybe (Address (LocationFor value) value) -> m () +addExport name alias address = modifyExports (exportInsert name (alias, address)) -- | A 'Monad' abstracting a heap of values. class Monad m => MonadStore value m | m -> value where diff --git a/src/Data/Syntax/Declaration.hs b/src/Data/Syntax/Declaration.hs index 0d29e9596..78a5ae9cc 100644 --- a/src/Data/Syntax/Declaration.hs +++ b/src/Data/Syntax/Declaration.hs @@ -223,7 +223,7 @@ instance Evaluatable QualifiedExport where eval (QualifiedExport exportSymbols) = do -- Insert the aliases with no addresses. for_ exportSymbols $ \(name, alias) -> - addExport name (alias, Nothing) + addExport name alias Nothing unit @@ -242,7 +242,7 @@ instance Evaluatable QualifiedExportFrom where -- Look up addresses in importedEnv and insert the aliases with addresses into the exports. for_ exportSymbols $ \(name, alias) -> do let address = Map.lookup name (unEnvironment importedEnv) - addExport name (alias, address) + addExport name alias address unit From 92d63dd1ff7475ee03b97fa2dc90b6ba529af675 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Thu, 15 Mar 2018 08:57:19 -0700 Subject: [PATCH 07/30] Better names --- src/Data/Syntax/Declaration.hs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Data/Syntax/Declaration.hs b/src/Data/Syntax/Declaration.hs index 78a5ae9cc..7783c8e53 100644 --- a/src/Data/Syntax/Declaration.hs +++ b/src/Data/Syntax/Declaration.hs @@ -275,9 +275,9 @@ instance Evaluatable QualifiedImport where where prefix = freeVariable (subterm alias) symbols = Map.fromList xs - copy = if Map.null symbols then copyAll else copySymbols - copyAll k v rest = envInsert (prefix <> k) v rest - copySymbols k v rest = maybe rest (\symAlias -> envInsert (prefix <> symAlias) v rest) (Map.lookup k symbols) + copy = if Map.null symbols then insert else maybeInsert + insert sym = envInsert (prefix <> sym) + maybeInsert sym v env = maybe env (\symAlias -> insert symAlias v env) (Map.lookup sym symbols) -- | Import declarations (symbols are added directly to the calling environment). @@ -298,9 +298,8 @@ instance Evaluatable Import where unit where symbols = Map.fromList xs - copy = if Map.null symbols then copyAll else copySymbols - copyAll = envInsert - copySymbols k v rest = maybe rest (\symAlias -> envInsert symAlias v rest) (Map.lookup k symbols) + copy = if Map.null symbols then envInsert else maybeInsert + maybeInsert k v env = maybe env (\symAlias -> envInsert symAlias v env) (Map.lookup k symbols) -- | Side effect only imports (no symbols made available to the calling environment). data SideEffectImport a = SideEffectImport { sideEffectImportFrom :: !a, sideEffectImportToken :: !a } From 1edce2cd17b456e59b349d1a557d30dd47c8100f Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Thu, 15 Mar 2018 09:02:58 -0700 Subject: [PATCH 08/30] Curry exportInsert too --- src/Control/Abstract/Evaluator.hs | 2 +- src/Data/Abstract/Environment.hs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Control/Abstract/Evaluator.hs b/src/Control/Abstract/Evaluator.hs index 9dcc9c58c..4483ee95d 100644 --- a/src/Control/Abstract/Evaluator.hs +++ b/src/Control/Abstract/Evaluator.hs @@ -70,7 +70,7 @@ modifyExports f = do -- | Add an export to the global export state. addExport :: MonadEnvironment value m => Name -> Name -> Maybe (Address (LocationFor value) value) -> m () -addExport name alias address = modifyExports (exportInsert name (alias, address)) +addExport name alias = modifyExports . exportInsert name alias -- | A 'Monad' abstracting a heap of values. class Monad m => MonadStore value m | m -> value where diff --git a/src/Data/Abstract/Environment.hs b/src/Data/Abstract/Environment.hs index 99c8a5064..611a90eaf 100644 --- a/src/Data/Abstract/Environment.hs +++ b/src/Data/Abstract/Environment.hs @@ -34,8 +34,8 @@ bindEnv :: (Ord l, Foldable t) => t Name -> Environment l a -> Environment l a bindEnv names env = foldMap envForName names where envForName name = maybe mempty (curry unit name) (envLookup name env) -exportInsert :: Name -> (Name, Maybe (Address l a)) -> Exports l a -> Exports l a -exportInsert name value = Exports . Map.insert name value . unExports +exportInsert :: Name -> Name -> Maybe (Address l a) -> Exports l a -> Exports l a +exportInsert name alias address = Exports . Map.insert name (alias, address) . unExports -- | Retrieve the 'Live' set of addresses to which the given free variable names are bound. -- From 432be15f9d38d21ce69b501f334926a3d0a88cd9 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Thu, 15 Mar 2018 10:32:37 -0700 Subject: [PATCH 09/30] Provide shortcut to isolating an action with localState --- src/Control/Abstract/Analysis.hs | 4 ++++ src/Control/Abstract/Evaluator.hs | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Control/Abstract/Analysis.hs b/src/Control/Abstract/Analysis.hs index 315e90802..3bd6e59bd 100644 --- a/src/Control/Abstract/Analysis.hs +++ b/src/Control/Abstract/Analysis.hs @@ -41,6 +41,10 @@ class (MonadEvaluator term value m, Recursive term) => MonadAnalysis term value evaluateModule :: term -> m value evaluateModule = evaluateTerm + -- | Isolate the given action with an empty global environment and exports. + isolate :: m a -> m a + isolate = withGlobalEnv mempty . withExports mempty + -- | Evaluate a term to a value using the semantics of the current analysis. -- -- This should always be called when e.g. evaluating the bodies of closures instead of explicitly folding either 'eval' or 'analyzeTerm' over subterms, except in 'MonadAnalysis' instances themselves. On the other hand, top-level evaluation should be performed using 'evaluateModule'. diff --git a/src/Control/Abstract/Evaluator.hs b/src/Control/Abstract/Evaluator.hs index 4483ee95d..d3eaf0b10 100644 --- a/src/Control/Abstract/Evaluator.hs +++ b/src/Control/Abstract/Evaluator.hs @@ -43,13 +43,14 @@ class Monad m => MonadEnvironment value m | m -> value where getGlobalEnv :: m (EnvironmentFor value) -- | Set the global environment putGlobalEnv :: EnvironmentFor value -> m () + -- | Sets the global environment for the lifetime of the given action. withGlobalEnv :: EnvironmentFor value -> m a -> m a -- | Get the global export state. getExports :: m (ExportsFor value) -- | Set the global export state. putExports :: ExportsFor value -> m () - -- | Sets the exports state to the given map for the lifetime of the given action. + -- | Sets the exports the lifetime of the given action. withExports :: ExportsFor value -> m a -> m a -- | Retrieve the local environment. From c46000c74d45fd3efa5407bd6f12fe26e19458ab Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Thu, 15 Mar 2018 10:33:08 -0700 Subject: [PATCH 10/30] Fail if trying to re-export something not defined in the imported module --- src/Data/Syntax/Declaration.hs | 8 ++++++-- test/Analysis/TypeScript/Spec.hs | 8 +++++++- test/fixtures/typescript/analysis/bad-export.ts | 2 ++ test/fixtures/typescript/analysis/pip.ts | 5 +++++ 4 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 test/fixtures/typescript/analysis/bad-export.ts create mode 100644 test/fixtures/typescript/analysis/pip.ts diff --git a/src/Data/Syntax/Declaration.hs b/src/Data/Syntax/Declaration.hs index 7783c8e53..e3ba3e3b4 100644 --- a/src/Data/Syntax/Declaration.hs +++ b/src/Data/Syntax/Declaration.hs @@ -5,6 +5,7 @@ import Data.Abstract.Environment import Data.Abstract.Evaluatable import Diffing.Algorithm import qualified Data.Map as Map +import Prelude hiding (fail) import Prologue data Function a = Function { functionContext :: ![a], functionName :: !a, functionParameters :: ![a], functionBody :: !a } @@ -238,12 +239,15 @@ instance Show1 QualifiedExportFrom where liftShowsPrec = genericLiftShowsPrec instance Evaluatable QualifiedExportFrom where eval (QualifiedExportFrom from exportSymbols) = do let moduleName = freeVariable (subterm from) - importedEnv <- withGlobalEnv mempty (require moduleName) + importedEnv <- isolate (require moduleName) -- Look up addresses in importedEnv and insert the aliases with addresses into the exports. for_ exportSymbols $ \(name, alias) -> do let address = Map.lookup name (unEnvironment importedEnv) - addExport name alias address + maybe (cannotExport moduleName name) (addExport name alias . Just) address unit + where + cannotExport moduleName name = fail $ + "module " <> show (friendlyName moduleName) <> " does not export " <> show (friendlyName name) newtype DefaultExport a = DefaultExport { defaultExport :: a } diff --git a/test/Analysis/TypeScript/Spec.hs b/test/Analysis/TypeScript/Spec.hs index 3ecc7962a..2c7d268a2 100644 --- a/test/Analysis/TypeScript/Spec.hs +++ b/test/Analysis/TypeScript/Spec.hs @@ -31,12 +31,18 @@ spec = parallel $ do env <- evaluate "main2.ts" env `shouldBe` Environment (fromList []) + it "fails exporting symbols not defined in the module" $ do + env <- fst <$> evaluate' "bad-export.ts" + env `shouldBe` Left "module \"foo\" does not export \"pip\"" + where addr = Address . Precise fixtures = "test/fixtures/typescript/analysis/" - evaluate entry = snd . fst . fst . fst <$> + evaluate entry = snd <$> evaluate' entry + evaluate' entry = fst . fst . fst <$> evaluateFiles @TypeScriptValue typescriptParser [ fixtures <> entry , fixtures <> "a.ts" , fixtures <> "foo.ts" + , fixtures <> "pip.ts" ] diff --git a/test/fixtures/typescript/analysis/bad-export.ts b/test/fixtures/typescript/analysis/bad-export.ts new file mode 100644 index 000000000..b8fac52e9 --- /dev/null +++ b/test/fixtures/typescript/analysis/bad-export.ts @@ -0,0 +1,2 @@ +export { pip } from "./pip" +export { pip } from "./foo" diff --git a/test/fixtures/typescript/analysis/pip.ts b/test/fixtures/typescript/analysis/pip.ts new file mode 100644 index 000000000..0203bfe04 --- /dev/null +++ b/test/fixtures/typescript/analysis/pip.ts @@ -0,0 +1,5 @@ +export { pip } + +function pip() { + return "this is the pip function" +} From ff2cc72549607a68a8aa495ba09527f8063dfbcc Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Thu, 15 Mar 2018 10:50:32 -0700 Subject: [PATCH 11/30] Use isolate for imports too --- src/Control/Abstract/Evaluator.hs | 3 ++- src/Data/Syntax/Declaration.hs | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Control/Abstract/Evaluator.hs b/src/Control/Abstract/Evaluator.hs index d3eaf0b10..9ad494513 100644 --- a/src/Control/Abstract/Evaluator.hs +++ b/src/Control/Abstract/Evaluator.hs @@ -50,7 +50,7 @@ class Monad m => MonadEnvironment value m | m -> value where getExports :: m (ExportsFor value) -- | Set the global export state. putExports :: ExportsFor value -> m () - -- | Sets the exports the lifetime of the given action. + -- | Sets the global export state for the lifetime of the given action. withExports :: ExportsFor value -> m a -> m a -- | Retrieve the local environment. @@ -64,6 +64,7 @@ modifyGlobalEnv f = do env <- getGlobalEnv putGlobalEnv $! f env +-- | Update the global export state. modifyExports :: MonadEnvironment value m => (ExportsFor value -> ExportsFor value) -> m () modifyExports f = do exports <- getExports diff --git a/src/Data/Syntax/Declaration.hs b/src/Data/Syntax/Declaration.hs index e3ba3e3b4..e0ee0e888 100644 --- a/src/Data/Syntax/Declaration.hs +++ b/src/Data/Syntax/Declaration.hs @@ -273,7 +273,7 @@ instance Show1 QualifiedImport where liftShowsPrec = genericLiftShowsPrec instance Evaluatable QualifiedImport where eval (QualifiedImport from alias xs) = do let moduleName = freeVariable (subterm from) - importedEnv <- withGlobalEnv mempty (require moduleName) + importedEnv <- isolate (require moduleName) modifyGlobalEnv (flip (Map.foldrWithKey copy) (unEnvironment importedEnv)) unit where @@ -297,7 +297,7 @@ instance Show1 Import where liftShowsPrec = genericLiftShowsPrec instance Evaluatable Import where eval (Import from xs _) = do let moduleName = freeVariable (subterm from) - importedEnv <- withGlobalEnv mempty (require moduleName) + importedEnv <- isolate (require moduleName) modifyGlobalEnv (flip (Map.foldrWithKey copy) (unEnvironment importedEnv)) unit where @@ -316,7 +316,7 @@ instance Show1 SideEffectImport where liftShowsPrec = genericLiftShowsPrec instance Evaluatable SideEffectImport where eval (SideEffectImport from _) = do let moduleName = freeVariable (subterm from) - void $ withGlobalEnv mempty (require moduleName) + void $ isolate (require moduleName) unit From 9e118aac51e7a024f838653811f1c63e361fdc45 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Tue, 13 Mar 2018 16:18:58 -0700 Subject: [PATCH 12/30] Some example ruby code for requires --- test/fixtures/ruby/analysis/foo.rb | 3 +++ test/fixtures/ruby/analysis/main.rb | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 test/fixtures/ruby/analysis/foo.rb create mode 100644 test/fixtures/ruby/analysis/main.rb 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/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) From 1e276dddefd6210d16649f93cc83bc168e36f80a Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Tue, 13 Mar 2018 16:19:26 -0700 Subject: [PATCH 13/30] TDD FTW --- semantic.cabal | 1 + test/Analysis/Ruby/Spec.hs | 29 +++++++++++++++++++++++++++++ test/Spec.hs | 2 ++ 3 files changed, 32 insertions(+) create mode 100644 test/Analysis/Ruby/Spec.hs diff --git a/semantic.cabal b/semantic.cabal index defc9f833..348e0add4 100644 --- a/semantic.cabal +++ b/semantic.cabal @@ -207,6 +207,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/test/Analysis/Ruby/Spec.hs b/test/Analysis/Ruby/Spec.hs new file mode 100644 index 000000000..3f13efb6f --- /dev/null +++ b/test/Analysis/Ruby/Spec.hs @@ -0,0 +1,29 @@ +{-# LANGUAGE TypeApplications #-} +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_relatives" $ do + res <- evaluate "main.rb" + let expectedEnv = Environment $ fromList [ (qualifiedName ["foo"], addr 0) ] + assertEnvironment res expectedEnv + + where + assertEnvironment result expectedEnv = case result of + Left e -> expectationFailure ("Evaluating expected to succeed, but failed with: " <> e) + Right res -> let Just (Interface _ env) = prjValue @(Interface Precise) res in env `shouldBe` expectedEnv + + addr = Address . Precise + fixtures = "test/fixtures/ruby/analysis/" + evaluate entry = fst . fst . fst . fst <$> + evaluateFiles @RubyValue rubyParser + [ fixtures <> entry + , fixtures <> "foo.rb" + ] 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 From 577bdcac6a716ba90dd462b0454ce8cde0dd0a43 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Tue, 13 Mar 2018 16:19:48 -0700 Subject: [PATCH 14/30] Focus on require_relative and add new Ruby specific Require syntax --- semantic.cabal | 1 + src/Control/Abstract/Value.hs | 6 ++++++ src/Language/Ruby/Assignment.hs | 13 ++++++++----- src/Language/Ruby/Syntax.hs | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 src/Language/Ruby/Syntax.hs diff --git a/semantic.cabal b/semantic.cabal index 348e0add4..e4fae94fd 100644 --- a/semantic.cabal +++ b/semantic.cabal @@ -98,6 +98,7 @@ library , Language.JSON.Assignment , Language.Ruby.Grammar , Language.Ruby.Assignment + , Language.Ruby.Syntax , Language.TypeScript.Assignment , Language.TypeScript.Grammar , Language.TypeScript.Syntax diff --git a/src/Control/Abstract/Value.hs b/src/Control/Abstract/Value.hs index 850c95a59..d54466c00 100644 --- a/src/Control/Abstract/Value.hs +++ b/src/Control/Abstract/Value.hs @@ -67,6 +67,8 @@ class (MonadAnalysis term value m, Show value) => MonadValue term value m where -- | Construct an N-ary tuple of multiple (possibly-disjoint) values multiple :: [value] -> m value + getString :: value -> m ByteString + -- | Eliminate boolean values. TODO: s/boolean/truthy ifthenelse :: value -> m a -> m a -> m a @@ -132,6 +134,10 @@ instance ( FreeVariables term multiple = pure . injValue . Value.Tuple + getString v + | Just (Value.String n) <- prjValue v = pure n + | otherwise = fail "not 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/Language/Ruby/Assignment.hs b/src/Language/Ruby/Assignment.hs index cb6271506..46cd9a854 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,7 @@ type Syntax = '[ , Syntax.Error , Syntax.Identifier , Syntax.Program + , Ruby.Syntax.Require , [] ] @@ -295,15 +296,17 @@ 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 <|> regularCall) + -- <|> makeTerm <$> symbol MethodCall <*> children (Expression.Call <$> pure [] <*> expression <*> args <*> (block <|> emptyTerm)) 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_relative"]) + -- guard (elem s ["autoload", "load", "require", "require_relative"]) + Ruby.Syntax.Require <$> nameExpression) args = (symbol ArgumentList <|> symbol ArgumentListWithParens) *> children (many expression) <|> pure [] - args' = makeTerm'' <$> (symbol ArgumentList <|> symbol ArgumentListWithParens) <*> children (many expression) <|> emptyTerm + 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..af2bd25eb --- /dev/null +++ b/src/Language/Ruby/Syntax.hs @@ -0,0 +1,33 @@ +{-# LANGUAGE DeriveAnyClass #-} +module Language.Ruby.Syntax where + +import Data.Abstract.Environment +import Data.Abstract.Evaluatable +import Diffing.Algorithm +import Prelude hiding (fail) +import Prologue +import qualified Data.Map as Map +import qualified Data.ByteString.Char8 as BC +import qualified Data.ByteString as B (filter) +import Data.Char (ord) + +data Require a = Require { 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 path) = do + v <- subtermValue path + path <- getString v + importedEnv <- withGlobalEnv mempty (require (pathToQualifiedName path)) + modifyGlobalEnv (flip (Map.foldrWithKey envInsert) (unEnvironment importedEnv)) + unit + where + pathToQualifiedName :: ByteString -> Name + pathToQualifiedName = qualifiedName . BC.split '/' . (BC.dropWhile (== '/')) . (BC.dropWhile (== '.')) . stripQuotes + + stripQuotes :: ByteString -> ByteString + stripQuotes = B.filter (/= (fromIntegral (ord '\"'))) From 92b383cf45f9b4912d750fce1054b9286d0ce670 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Thu, 15 Mar 2018 12:29:08 -0700 Subject: [PATCH 15/30] Extract some comment path to qualified name helpers --- src/Data/Abstract/FreeVariables.hs | 15 +++++++++++++++ src/Language/Go/Assignment.hs | 5 +---- src/Language/Ruby/Syntax.hs | 14 +++----------- src/Language/TypeScript/Assignment.hs | 9 --------- test/Analysis/Ruby/Spec.hs | 12 ++++-------- 5 files changed, 23 insertions(+), 32 deletions(-) diff --git a/src/Data/Abstract/FreeVariables.hs b/src/Data/Abstract/FreeVariables.hs index 2e73218c2..19d319cd5 100644 --- a/src/Data/Abstract/FreeVariables.hs +++ b/src/Data/Abstract/FreeVariables.hs @@ -5,16 +5,31 @@ import Prologue import Data.Term import Data.ByteString (intercalate) import qualified Data.List.NonEmpty as NonEmpty +import qualified Data.ByteString.Char8 as BC +import qualified Data.ByteString as B (filter) +import Data.Char (ord) -- | 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 +-- | Split a 'ByteString' path on `/`, stripping quotes and any `./` prefix. +splitOnPathSeparator :: ByteString -> [ByteString] +splitOnPathSeparator = BC.split '/' . BC.dropWhile (== '/') . BC.dropWhile (== '.') . stripQuotes + where stripQuotes = B.filter (/= fromIntegral (ord '\"')) + +-- | Construct a qualified 'Name' from a `/` delimited path. +pathToQualifiedName :: ByteString -> Name +pathToQualifiedName = qualifiedName . splitOnPathSeparator + +-- | User friendly version of a qualified 'Name'. friendlyName :: Name -> ByteString friendlyName xs = intercalate "." (NonEmpty.toList xs) diff --git a/src/Language/Go/Assignment.hs b/src/Language/Go/Assignment.hs index 7bfad97b8..575f5bef5 100644 --- a/src/Language/Go/Assignment.hs +++ b/src/Language/Go/Assignment.hs @@ -395,7 +395,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 +406,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/Syntax.hs b/src/Language/Ruby/Syntax.hs index af2bd25eb..211b708f1 100644 --- a/src/Language/Ruby/Syntax.hs +++ b/src/Language/Ruby/Syntax.hs @@ -2,16 +2,14 @@ module Language.Ruby.Syntax where import Data.Abstract.Environment +import Data.Abstract.FreeVariables import Data.Abstract.Evaluatable import Diffing.Algorithm -import Prelude hiding (fail) +-- import Prelude hiding (fail) import Prologue import qualified Data.Map as Map -import qualified Data.ByteString.Char8 as BC -import qualified Data.ByteString as B (filter) -import Data.Char (ord) -data Require a = Require { requirePath :: !a } +newtype Require a = Require { requirePath :: a } deriving (Diffable, Eq, Foldable, Functor, GAlign, Generic1, Mergeable, Ord, Show, Traversable, FreeVariables1) instance Eq1 Require where liftEq = genericLiftEq @@ -25,9 +23,3 @@ instance Evaluatable Require where importedEnv <- withGlobalEnv mempty (require (pathToQualifiedName path)) modifyGlobalEnv (flip (Map.foldrWithKey envInsert) (unEnvironment importedEnv)) unit - where - pathToQualifiedName :: ByteString -> Name - pathToQualifiedName = qualifiedName . BC.split '/' . (BC.dropWhile (== '/')) . (BC.dropWhile (== '.')) . stripQuotes - - stripQuotes :: ByteString -> ByteString - stripQuotes = B.filter (/= (fromIntegral (ord '\"'))) 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/Ruby/Spec.hs b/test/Analysis/Ruby/Spec.hs index 3f13efb6f..58418abd6 100644 --- a/test/Analysis/Ruby/Spec.hs +++ b/test/Analysis/Ruby/Spec.hs @@ -10,19 +10,15 @@ import SpecHelpers spec :: Spec spec = parallel $ do describe "evalutes Ruby" $ do - it "require_relatives" $ do - res <- evaluate "main.rb" + it "require_relative method" $ do + env <- evaluate "main.rb" let expectedEnv = Environment $ fromList [ (qualifiedName ["foo"], addr 0) ] - assertEnvironment res expectedEnv + env `shouldBe` expectedEnv where - assertEnvironment result expectedEnv = case result of - Left e -> expectationFailure ("Evaluating expected to succeed, but failed with: " <> e) - Right res -> let Just (Interface _ env) = prjValue @(Interface Precise) res in env `shouldBe` expectedEnv - addr = Address . Precise fixtures = "test/fixtures/ruby/analysis/" - evaluate entry = fst . fst . fst . fst <$> + evaluate entry = snd . fst . fst . fst <$> evaluateFiles @RubyValue rubyParser [ fixtures <> entry , fixtures <> "foo.rb" From 5dfb19cd29e1a698a777239788ecfedb188adaa8 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Thu, 15 Mar 2018 12:29:46 -0700 Subject: [PATCH 16/30] Docs, function ordering --- src/Data/Abstract/FreeVariables.hs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Data/Abstract/FreeVariables.hs b/src/Data/Abstract/FreeVariables.hs index 19d319cd5..f96270244 100644 --- a/src/Data/Abstract/FreeVariables.hs +++ b/src/Data/Abstract/FreeVariables.hs @@ -20,16 +20,16 @@ name x = x :| [] qualifiedName :: [ByteString] -> Name qualifiedName = NonEmpty.fromList +-- | Construct a qualified 'Name' from a `/` delimited path. +pathToQualifiedName :: ByteString -> Name +pathToQualifiedName = qualifiedName . splitOnPathSeparator + -- | Split a 'ByteString' path on `/`, stripping quotes and any `./` prefix. splitOnPathSeparator :: ByteString -> [ByteString] splitOnPathSeparator = BC.split '/' . BC.dropWhile (== '/') . BC.dropWhile (== '.') . stripQuotes where stripQuotes = B.filter (/= fromIntegral (ord '\"')) --- | Construct a qualified 'Name' from a `/` delimited path. -pathToQualifiedName :: ByteString -> Name -pathToQualifiedName = qualifiedName . splitOnPathSeparator - --- | User friendly version of a qualified 'Name'. +-- | User friendly 'ByteString' of a qualified 'Name'. friendlyName :: Name -> ByteString friendlyName xs = intercalate "." (NonEmpty.toList xs) From 6f738ee6256e7b06a4bf68836716b579f7ea5ac1 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Thu, 15 Mar 2018 15:06:11 -0700 Subject: [PATCH 17/30] Bring a bunch of path parsing helpers into one place --- src/Data/Abstract/FreeVariables.hs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/Data/Abstract/FreeVariables.hs b/src/Data/Abstract/FreeVariables.hs index f96270244..c5c30c6dd 100644 --- a/src/Data/Abstract/FreeVariables.hs +++ b/src/Data/Abstract/FreeVariables.hs @@ -6,7 +6,7 @@ import Data.Term import Data.ByteString (intercalate) import qualified Data.List.NonEmpty as NonEmpty import qualified Data.ByteString.Char8 as BC -import qualified Data.ByteString as B (filter) +import qualified Data.ByteString as B import Data.Char (ord) -- | The type of variable names. @@ -26,8 +26,21 @@ pathToQualifiedName = qualifiedName . splitOnPathSeparator -- | Split a 'ByteString' path on `/`, stripping quotes and any `./` prefix. splitOnPathSeparator :: ByteString -> [ByteString] -splitOnPathSeparator = BC.split '/' . BC.dropWhile (== '/') . BC.dropWhile (== '.') . stripQuotes - where stripQuotes = B.filter (/= fromIntegral (ord '\"')) +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) -- | User friendly 'ByteString' of a qualified 'Name'. friendlyName :: Name -> ByteString From f72d7f20dfa30ee24292e66069a547747a9c90d2 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Thu, 15 Mar 2018 15:06:27 -0700 Subject: [PATCH 18/30] Show the friendly qualified name for free variable error msg --- src/Data/Syntax.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Data/Syntax.hs b/src/Data/Syntax.hs index 0e0735b33..40e943b55 100644 --- a/src/Data/Syntax.hs +++ b/src/Data/Syntax.hs @@ -110,7 +110,7 @@ instance Show1 Identifier where liftShowsPrec = genericLiftShowsPrec instance Evaluatable Identifier where eval (Identifier name) = do env <- askLocalEnv - maybe (fail ("free variable: " <> show name)) deref (envLookup name env) + maybe (fail ("free variable: " <> show (friendlyName name))) deref (envLookup name env) instance FreeVariables1 Identifier where liftFreeVariables _ (Identifier x) = Set.singleton x From 756eff53823749d7128cfe6a76b93a84a1d7907c Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Thu, 15 Mar 2018 15:07:41 -0700 Subject: [PATCH 19/30] Change up the shape of require for relative vs not --- src/Control/Abstract/Value.hs | 7 ++++--- src/Language/Ruby/Assignment.hs | 5 ++--- src/Language/Ruby/Syntax.hs | 9 ++++----- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/Control/Abstract/Value.hs b/src/Control/Abstract/Value.hs index d54466c00..00498e4d4 100644 --- a/src/Control/Abstract/Value.hs +++ b/src/Control/Abstract/Value.hs @@ -67,7 +67,7 @@ class (MonadAnalysis term value m, Show value) => MonadValue term value m where -- | Construct an N-ary tuple of multiple (possibly-disjoint) values multiple :: [value] -> m value - getString :: value -> m ByteString + asString :: value -> m ByteString -- | Eliminate boolean values. TODO: s/boolean/truthy ifthenelse :: value -> m a -> m a -> m a @@ -134,9 +134,10 @@ instance ( FreeVariables term multiple = pure . injValue . Value.Tuple - getString v + asString v | Just (Value.String n) <- prjValue v = pure n - | otherwise = fail "not a string" + | otherwise = fail ("expected " <> show v <> " to be a string") + ifthenelse cond if' else' | Just (Boolean b) <- prjValue cond = if b then if' else else' diff --git a/src/Language/Ruby/Assignment.hs b/src/Language/Ruby/Assignment.hs index 46cd9a854..59dcb6cc7 100644 --- a/src/Language/Ruby/Assignment.hs +++ b/src/Language/Ruby/Assignment.hs @@ -302,9 +302,8 @@ methodCall = makeTerm' <$> symbol MethodCall <*> children (require <|> regular regularCall = inj <$> (Expression.Call <$> pure [] <*> expression <*> args <*> (block <|> emptyTerm)) require = inj <$> (symbol Identifier *> do s <- source - guard (elem s ["require_relative"]) - -- guard (elem s ["autoload", "load", "require", "require_relative"]) - Ruby.Syntax.Require <$> nameExpression) + guard (elem s ["require", "require_relative"]) + Ruby.Syntax.Require (s == "require_relative") <$> nameExpression) args = (symbol ArgumentList <|> symbol ArgumentListWithParens) *> children (many expression) <|> pure [] nameExpression = (symbol ArgumentList <|> symbol ArgumentListWithParens) *> children expression diff --git a/src/Language/Ruby/Syntax.hs b/src/Language/Ruby/Syntax.hs index 211b708f1..3bc626a9d 100644 --- a/src/Language/Ruby/Syntax.hs +++ b/src/Language/Ruby/Syntax.hs @@ -9,7 +9,7 @@ import Diffing.Algorithm import Prologue import qualified Data.Map as Map -newtype Require a = Require { requirePath :: a } +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 @@ -17,9 +17,8 @@ instance Ord1 Require where liftCompare = genericLiftCompare instance Show1 Require where liftShowsPrec = genericLiftShowsPrec instance Evaluatable Require where - eval (Require path) = do - v <- subtermValue path - path <- getString v - importedEnv <- withGlobalEnv mempty (require (pathToQualifiedName path)) + eval (Require _ x) = do + name <- pathToQualifiedName <$> (subtermValue x >>= asString) + importedEnv <- isolate (require name) modifyGlobalEnv (flip (Map.foldrWithKey envInsert) (unEnvironment importedEnv)) unit From 572f05703708fe7a0d309cd20e2189794a4cc58c Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Thu, 15 Mar 2018 15:08:03 -0700 Subject: [PATCH 20/30] Different spec name --- test/Analysis/Ruby/Spec.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Analysis/Ruby/Spec.hs b/test/Analysis/Ruby/Spec.hs index 58418abd6..dcdadcf3a 100644 --- a/test/Analysis/Ruby/Spec.hs +++ b/test/Analysis/Ruby/Spec.hs @@ -10,7 +10,7 @@ import SpecHelpers spec :: Spec spec = parallel $ do describe "evalutes Ruby" $ do - it "require_relative method" $ do + it "require_relative" $ do env <- evaluate "main.rb" let expectedEnv = Environment $ fromList [ (qualifiedName ["foo"], addr 0) ] env `shouldBe` expectedEnv From e5e72b36e90950fa6d9d183dac534804d8bdfa59 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Thu, 15 Mar 2018 15:09:07 -0700 Subject: [PATCH 21/30] Some ruby load specs and fixtures --- test/Analysis/Ruby/Spec.hs | 13 ++++++++++++- test/fixtures/ruby/analysis/load-wrap.rb | 3 +++ test/fixtures/ruby/analysis/load.rb | 3 +++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/ruby/analysis/load-wrap.rb create mode 100644 test/fixtures/ruby/analysis/load.rb diff --git a/test/Analysis/Ruby/Spec.hs b/test/Analysis/Ruby/Spec.hs index dcdadcf3a..f844f5515 100644 --- a/test/Analysis/Ruby/Spec.hs +++ b/test/Analysis/Ruby/Spec.hs @@ -15,10 +15,21 @@ spec = parallel $ do 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 . fst . fst . fst <$> + evaluate entry = snd <$> evaluate' entry + evaluate' entry = fst . fst . fst <$> evaluateFiles @RubyValue rubyParser [ fixtures <> entry , fixtures <> "foo.rb" 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) From f70ef24015f76a6a8b15b5689e47e79b4f2d445b Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Thu, 15 Mar 2018 15:09:28 -0700 Subject: [PATCH 22/30] Assign and eval ruby's load syntax --- src/Analysis/Declaration.hs | 53 +++++++++++++++++++-------------- src/Control/Abstract/Value.hs | 4 +++ src/Language/Ruby/Assignment.hs | 8 ++++- src/Language/Ruby/Syntax.hs | 33 ++++++++++++++++++-- test/SpecHelpers.hs | 2 +- 5 files changed, 73 insertions(+), 27 deletions(-) diff --git a/src/Analysis/Declaration.hs b/src/Analysis/Declaration.hs index bf4a727b1..94fd6526c 100644 --- a/src/Analysis/Declaration.hs +++ b/src/Analysis/Declaration.hs @@ -14,10 +14,11 @@ import Data.Record import Data.Source as Source import Data.Span import Data.Term -import Data.Abstract.FreeVariables +import Data.Abstract.FreeVariables hiding (stripQuotes) 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 00498e4d4..0e3b3a786 100644 --- a/src/Control/Abstract/Value.hs +++ b/src/Control/Abstract/Value.hs @@ -68,6 +68,7 @@ class (MonadAnalysis term value m, Show value) => MonadValue term value m where multiple :: [value] -> m value asString :: value -> m ByteString + asBool :: value -> m Bool -- | Eliminate boolean values. TODO: s/boolean/truthy ifthenelse :: value -> m a -> m a -> m a @@ -138,6 +139,9 @@ instance ( FreeVariables term | Just (Value.String n) <- prjValue v = pure n | otherwise = fail ("expected " <> show v <> " to be a string") + asBool v + | Just (Value.Boolean x) <- prjValue v = pure x + | otherwise = fail ("expected " <> show v <> " to be a boolean") ifthenelse cond if' else' | Just (Boolean b) <- prjValue cond = if b then if' else else' diff --git a/src/Language/Ruby/Assignment.hs b/src/Language/Ruby/Assignment.hs index 59dcb6cc7..82dad8372 100644 --- a/src/Language/Ruby/Assignment.hs +++ b/src/Language/Ruby/Assignment.hs @@ -75,6 +75,7 @@ type Syntax = '[ , Syntax.Identifier , Syntax.Program , Ruby.Syntax.Require + , Ruby.Syntax.Load , [] ] @@ -296,7 +297,7 @@ 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) -- <|> makeTerm <$> symbol MethodCall <*> children (Expression.Call <$> pure [] <*> expression <*> args <*> (block <|> emptyTerm)) where regularCall = inj <$> (Expression.Call <$> pure [] <*> expression <*> args <*> (block <|> emptyTerm)) @@ -304,7 +305,12 @@ methodCall = makeTerm' <$> symbol MethodCall <*> children (require <|> regular s <- source 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 [] + loadArgs = (symbol ArgumentList <|> symbol ArgumentListWithParens) *> children (some expression) nameExpression = (symbol ArgumentList <|> symbol ArgumentListWithParens) *> children expression call :: Assignment diff --git a/src/Language/Ruby/Syntax.hs b/src/Language/Ruby/Syntax.hs index 3bc626a9d..a49464988 100644 --- a/src/Language/Ruby/Syntax.hs +++ b/src/Language/Ruby/Syntax.hs @@ -1,11 +1,13 @@ {-# LANGUAGE DeriveAnyClass #-} module Language.Ruby.Syntax where +import Control.Monad (unless) +import Control.Abstract.Value (MonadValue) import Data.Abstract.Environment -import Data.Abstract.FreeVariables import Data.Abstract.Evaluatable +import Data.Abstract.Value (LocationFor) import Diffing.Algorithm --- import Prelude hiding (fail) +import Prelude hiding (fail) import Prologue import qualified Data.Map as Map @@ -22,3 +24,30 @@ instance Evaluatable Require where 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 >>= asBool + doLoad path shouldWrap + eval (Load _) = fail "invalid argument supplied to load, path is required" + +doLoad :: (MonadAnalysis term value m, MonadValue term 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/test/SpecHelpers.hs b/test/SpecHelpers.hs index d3752cf8a..3010a83d5 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.ModuleTable as X import Data.Abstract.Store as X import Data.Blob as X From 55c79cb8dfa334127ebbc402022b0c790ce1e206 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Thu, 15 Mar 2018 15:09:53 -0700 Subject: [PATCH 23/30] Slight change to ruby require parse trees --- test/fixtures/ruby/require.diffA-B.txt | 19 ++++++++----------- test/fixtures/ruby/require.diffB-A.txt | 19 ++++++++----------- test/fixtures/ruby/require.parseA.txt | 5 ++--- test/fixtures/ruby/require.parseB.txt | 11 +++++------ 4 files changed, 23 insertions(+), 31 deletions(-) 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))) From 9b37eb51a59d609bb259362130d04ea70dc9fe63 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Thu, 15 Mar 2018 15:15:11 -0700 Subject: [PATCH 24/30] Remove extraneous comment --- src/Language/Ruby/Assignment.hs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Language/Ruby/Assignment.hs b/src/Language/Ruby/Assignment.hs index 82dad8372..8157e9262 100644 --- a/src/Language/Ruby/Assignment.hs +++ b/src/Language/Ruby/Assignment.hs @@ -297,8 +297,7 @@ pair :: Assignment pair = makeTerm <$> symbol Pair <*> children (Literal.KeyValue <$> expression <*> (expression <|> emptyTerm)) methodCall :: Assignment -methodCall = makeTerm' <$> symbol MethodCall <*> children (require <|> load <|> regularCall) - -- <|> makeTerm <$> symbol MethodCall <*> children (Expression.Call <$> pure [] <*> expression <*> args <*> (block <|> emptyTerm)) +methodCall = makeTerm' <$> symbol MethodCall <*> children (require <|> load <|> regularCall) where regularCall = inj <$> (Expression.Call <$> pure [] <*> expression <*> args <*> (block <|> emptyTerm)) require = inj <$> (symbol Identifier *> do From a40dc8825e71e9733c88e42b322fa384515c3b58 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Thu, 15 Mar 2018 15:28:41 -0700 Subject: [PATCH 25/30] One more fst --- test/Analysis/TypeScript/Spec.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Analysis/TypeScript/Spec.hs b/test/Analysis/TypeScript/Spec.hs index c57964aeb..b8cf3b7e7 100644 --- a/test/Analysis/TypeScript/Spec.hs +++ b/test/Analysis/TypeScript/Spec.hs @@ -39,7 +39,7 @@ spec = parallel $ do addr = Address . Precise fixtures = "test/fixtures/typescript/analysis/" evaluate entry = snd <$> evaluate' entry - evaluate' entry = fst . fst . fst <$> + evaluate' entry = fst . fst . fst . fst <$> evaluateFiles typescriptParser [ fixtures <> entry , fixtures <> "a.ts" From a580e1d65b686e928a4d5ef7c9243dd3cd10a128 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Thu, 15 Mar 2018 15:37:17 -0700 Subject: [PATCH 26/30] One more piece of state now --- test/Analysis/Ruby/Spec.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Analysis/Ruby/Spec.hs b/test/Analysis/Ruby/Spec.hs index f844f5515..08520e098 100644 --- a/test/Analysis/Ruby/Spec.hs +++ b/test/Analysis/Ruby/Spec.hs @@ -29,8 +29,8 @@ spec = parallel $ do addr = Address . Precise fixtures = "test/fixtures/ruby/analysis/" evaluate entry = snd <$> evaluate' entry - evaluate' entry = fst . fst . fst <$> - evaluateFiles @RubyValue rubyParser + evaluate' entry = fst . fst . fst . fst <$> + evaluateFiles rubyParser [ fixtures <> entry , fixtures <> "foo.rb" ] From 38fb63f660f38009346bbcac1be0717b546373ea Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Fri, 16 Mar 2018 09:03:37 -0700 Subject: [PATCH 27/30] TypeApplications no longer necessary here --- test/Analysis/Go/Spec.hs | 1 - test/Analysis/Python/Spec.hs | 1 - test/Analysis/Ruby/Spec.hs | 1 - test/Analysis/TypeScript/Spec.hs | 1 - 4 files changed, 4 deletions(-) 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 index 08520e098..332ea2725 100644 --- a/test/Analysis/Ruby/Spec.hs +++ b/test/Analysis/Ruby/Spec.hs @@ -1,4 +1,3 @@ -{-# LANGUAGE TypeApplications #-} module Analysis.Ruby.Spec (spec) where import Data.Abstract.Value 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 From 197c8d8506462f0882fedc53bff4367c1e1724cc Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Fri, 16 Mar 2018 09:16:23 -0700 Subject: [PATCH 28/30] Use existing toBool instead --- src/Control/Abstract/Value.hs | 8 ++------ src/Language/Ruby/Syntax.hs | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Control/Abstract/Value.hs b/src/Control/Abstract/Value.hs index 09f3ff093..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,8 +80,8 @@ 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 - asBool :: value -> m Bool -- | Eliminate boolean values. TODO: s/boolean/truthy ifthenelse :: value -> m a -> m a -> m a @@ -152,10 +152,6 @@ instance ( Monad m | Just (Value.String n) <- prjValue v = pure n | otherwise = fail ("expected " <> show v <> " to be a string") - asBool v - | Just (Value.Boolean x) <- prjValue v = pure x - | otherwise = fail ("expected " <> show v <> " to be a boolean") - 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/Language/Ruby/Syntax.hs b/src/Language/Ruby/Syntax.hs index e454f4cce..fac1125c8 100644 --- a/src/Language/Ruby/Syntax.hs +++ b/src/Language/Ruby/Syntax.hs @@ -38,7 +38,7 @@ instance Evaluatable Load where doLoad path False eval (Load [x, wrap]) = do path <- subtermValue x >>= asString - shouldWrap <- subtermValue wrap >>= asBool + shouldWrap <- subtermValue wrap >>= toBool doLoad path shouldWrap eval (Load _) = fail "invalid argument supplied to load, path is required" From 090479667416da90bf93ce2e2f578df151290b7c Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Fri, 16 Mar 2018 09:27:08 -0700 Subject: [PATCH 29/30] Add a specific Data.Abstract.Path with path/module name helpers --- semantic.cabal | 1 + src/Data/Abstract/FreeVariables.hs | 22 +--------------------- src/Data/Abstract/Path.hs | 24 ++++++++++++++++++++++++ src/Language/Go/Assignment.hs | 1 + src/Language/Ruby/Syntax.hs | 1 + 5 files changed, 28 insertions(+), 21 deletions(-) create mode 100644 src/Data/Abstract/Path.hs diff --git a/semantic.cabal b/semantic.cabal index 91d4b2c02..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 diff --git a/src/Data/Abstract/FreeVariables.hs b/src/Data/Abstract/FreeVariables.hs index 683f5134b..fbfbfc079 100644 --- a/src/Data/Abstract/FreeVariables.hs +++ b/src/Data/Abstract/FreeVariables.hs @@ -5,9 +5,7 @@ import Prologue import Data.Term import Data.ByteString (intercalate) import qualified Data.List.NonEmpty as NonEmpty -import qualified Data.ByteString.Char8 as BC -import qualified Data.ByteString as B -import Data.Char (ord) +import Data.Abstract.Path -- | The type of variable names. type Name = NonEmpty ByteString @@ -24,24 +22,6 @@ qualifiedName = NonEmpty.fromList pathToQualifiedName :: ByteString -> Name pathToQualifiedName = qualifiedName . splitOnPathSeparator --- | 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) - -- | 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/Language/Go/Assignment.hs b/src/Language/Go/Assignment.hs index b27675671..57441a432 100644 --- a/src/Language/Go/Assignment.hs +++ b/src/Language/Go/Assignment.hs @@ -8,6 +8,7 @@ module Language.Go.Assignment import Assigning.Assignment hiding (Assignment, Error) import Data.Abstract.FreeVariables +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 diff --git a/src/Language/Ruby/Syntax.hs b/src/Language/Ruby/Syntax.hs index fac1125c8..753f3273b 100644 --- a/src/Language/Ruby/Syntax.hs +++ b/src/Language/Ruby/Syntax.hs @@ -5,6 +5,7 @@ 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) From c76eece89dea2d3d441529af2af5b72c1d0d5d74 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Fri, 16 Mar 2018 09:28:03 -0700 Subject: [PATCH 30/30] No need to hide this now --- src/Analysis/Declaration.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analysis/Declaration.hs b/src/Analysis/Declaration.hs index 94fd6526c..c83a0d6a1 100644 --- a/src/Analysis/Declaration.hs +++ b/src/Analysis/Declaration.hs @@ -14,7 +14,7 @@ import Data.Record import Data.Source as Source import Data.Span import Data.Term -import Data.Abstract.FreeVariables hiding (stripQuotes) +import Data.Abstract.FreeVariables import qualified Data.Syntax as Syntax import qualified Data.Syntax.Declaration as Declaration import qualified Data.Syntax.Expression as Expression