git.iko.soy/rowtype-yoga/purescript-native
on golang help sign in
Merge pull request #1307 from hdgarrood/bundle-error-reporting

Improved error reporting in psc-bundle

commit24eb832b92d9b35180fdc885f0ef4526a570b9ff authorPhil Freeman <paf31@cantab.net> dateSun 26 Jul 2015, 09:02 (11 years ago) parentda99bc165c7718
1 file changed+122−115
src/Language/PureScript/Bundle.hs+122−115
src/Language/PureScript/Bundle.hs+122−115
@@ -50,17 +50,17 @@ import qualified Paths_purescript as Paths
5050data ErrorMessage
5151 = UnsupportedModulePath String
5252 | InvalidTopLevel
53− | UnableToParseModule
53+ | UnableToParseModule String
5454 | UnsupportedExport
55− | ErrorInFile FilePath ErrorMessage
55+ | ErrorInModule ModuleIdentifier ErrorMessage
5656 deriving Show
5757
58−-- | Modules are either "regular modules" (i.e. those generated by psc-make) or foreign modules.
59−data ModuleType
60− = Regular
61− | Foreign
58+-- | Modules are either "regular modules" (i.e. those generated by psc) or foreign modules.
59+data ModuleType
60+ = Regular
61+ | Foreign
6262 deriving (Show, Eq, Ord)
63−
63+
6464-- | A module is identified by its module name and its type.
6565data ModuleIdentifier = ModuleIdentifier String ModuleType deriving (Show, Eq, Ord)
6666
@@ -73,11 +73,11 @@ type Key = (ModuleIdentifier, String)
7373
7474-- | An export is either a "regular export", which exports a name from the regular module we are in,
7575-- or a reexport of a declaration in the corresponding foreign module.
76−--
77−-- Regular exports are labelled, since they might re-export an operator with another name.
78−data ExportType
79− = RegularExport String
80− | ForeignReexport
76+--
77+-- Regular exports are labelled, since they might re-export an operator with another name.
78+data ExportType
79+ = RegularExport String
80+ | ForeignReexport
8181 deriving (Show, Eq, Ord)
8282
8383-- | There are four types of module element we are interested in:
@@ -89,37 +89,42 @@ data ExportType
8989--
9090-- Each is labelled with the original AST node which generated it, so that we can dump it back
9191-- into the output during codegen.
92−data ModuleElement
92+data ModuleElement
9393 = Require JSNode String ModuleIdentifier
9494 | Member JSNode Bool String [JSNode] [Key]
9595 | ExportsList [(ExportType, String, JSNode, [Key])]
9696 | Other JSNode
9797 deriving Show
9898
99−-- | A module is just a list of elements of the types listed above.
99+-- | A module is just a list of elements of the types listed above.
100100data Module = Module ModuleIdentifier [ModuleElement] deriving Show
101−
101+
102102-- | Prepare an error message for consumption by humans.
103103printErrorMessage :: ErrorMessage -> [String]
104104printErrorMessage (UnsupportedModulePath s) =
105105 [ "A CommonJS module has an unsupported name (" ++ show s ++ ")."
106106 , "The following file names are supported:"
107− , " 1) index.js (psc-make native modules)"
108− , " 2) foreign.js (psc-make foreign modules)"
107+ , " 1) index.js (psc native modules)"
108+ , " 2) foreign.js (psc foreign modules)"
109109 ]
110110printErrorMessage InvalidTopLevel =
111− [ "Expected a list of source elements at the top level." ]
112−printErrorMessage UnableToParseModule =
113− [ "The module could not be parsed." ]
111+ [ "Expected a list of source elements at the top level." ]
112+printErrorMessage (UnableToParseModule err) =
113+ [ "The module could not be parsed:"
114+ , err
115+ ]
114116printErrorMessage UnsupportedExport =
115117 [ "An export was unsupported. Exports can be defined in one of two ways: "
116118 , " 1) exports.name = ..."
117119 , " 2) exports = { ... }"
118120 ]
119−printErrorMessage (ErrorInFile filename e) =
120− ("Error in file " ++ show filename ++ ":")
121+printErrorMessage (ErrorInModule mid e) =
122+ ("Error in module " ++ displayIdentifier mid ++ ":")
121123 : ""
122124 : map (" " ++) (printErrorMessage e)
125+ where
126+ displayIdentifier (ModuleIdentifier name ty) =
127+ name ++ " (" ++ show ty ++ ")"
123128
124129-- | Unpack the node inside a JSNode. This is useful when pattern matching.
125130node :: JSNode -> Node
@@ -128,18 +133,18 @@ node (NT n _ _) = n
128133
129134-- | Calculate the ModuleIdentifier which a require(...) statement imports.
130135checkImportPath :: String -> ModuleIdentifier -> S.Set String -> Maybe ModuleIdentifier
131−checkImportPath "./foreign" m _ =
136+checkImportPath "./foreign" m _ =
132137 Just (ModuleIdentifier (moduleName m) Foreign)
133−checkImportPath name _ names
138+checkImportPath name _ names
134139 | name `S.member` names = Just (ModuleIdentifier name Regular)
135140checkImportPath _ _ _ = Nothing
136141
137142-- | Compute the dependencies of all elements in a module, and add them to the tree.
138−--
143+--
139144-- Members and exports can have dependencies. A dependency is of one of the following forms:
140145--
141146-- 1) module.name or member["name"]
142−--
147+--
143148-- where module was imported using
144149--
145150-- var module = require("Module.Name");
@@ -149,7 +154,7 @@ checkImportPath _ _ _ = Nothing
149154-- where name is the name of a member defined in the current module.
150155withDeps :: Module -> Module
151156withDeps (Module modulePath es) = Module modulePath (map expandDeps es)
152− where
157+ where
153158 -- | Collects all modules which are imported, so that we can identify dependencies of the first type.
154159 imports :: [(String, ModuleIdentifier)]
155160 imports = mapMaybe toImport es
@@ -157,7 +162,7 @@ withDeps (Module modulePath es) = Module modulePath (map expandDeps es)
157162 toImport :: ModuleElement -> Maybe (String, ModuleIdentifier)
158163 toImport (Require _ nm mid) = Just (nm, mid)
159164 toImport _ = Nothing
160−
165+
161166 -- | Collects all member names in scope, so that we can identify dependencies of the second type.
162167 boundNames :: [String]
163168 boundNames = mapMaybe toBoundName es
@@ -173,7 +178,7 @@ withDeps (Module modulePath es) = Module modulePath (map expandDeps es)
173178 where
174179 expand (ty, nm, n1, _) = (ty, nm, n1, nub (dependencies modulePath n1))
175180 expandDeps other = other
176−
181+
177182 dependencies :: ModuleIdentifier -> JSNode -> [(ModuleIdentifier, String)]
178183 dependencies m = everything (++) (mkQ [] toReference)
179184 where
@@ -195,29 +200,31 @@ withDeps (Module modulePath es) = Module modulePath (map expandDeps es)
195200 toReference _ = []
196201
197202-- | Attempt to create a Module from a Javascript AST.
198−--
199−-- Each type of module element is matched using pattern guards, and everything else is bundled into the
203+--
204+-- Each type of module element is matched using pattern guards, and everything else is bundled into the
200205-- Other constructor.
201206toModule :: forall m. (Applicative m, MonadError ErrorMessage m) => S.Set String -> ModuleIdentifier -> JSNode -> m Module
202−toModule mids mid top
207+toModule mids mid top
203208 | JSSourceElementsTop ns <- node top = Module mid <$> mapM toModuleElement ns
204− | otherwise = throwError InvalidTopLevel
205− where
209+ | otherwise = err InvalidTopLevel
210+ where
211+ err = throwError . ErrorInModule mid
212+
206213 toModuleElement :: JSNode -> m ModuleElement
207214 toModuleElement n
208215 | JSVariables var [ varIntro ] _ <- node n
209− , JSLiteral "var" <- node var
216+ , JSLiteral "var" <- node var
210217 , JSVarDecl impN [ eq, req, impP ] <- node varIntro
211218 , JSIdentifier importName <- node impN
212219 , JSLiteral "=" <- node eq
213220 , JSIdentifier "require" <- node req
214− , JSArguments _ [ impS ] _ <- node impP
221+ , JSArguments _ [ impS ] _ <- node impP
215222 , JSStringLiteral _ importPath <- node impS
216223 , Just importPath' <- checkImportPath importPath mid mids
217224 = pure (Require n importName importPath')
218225 toModuleElement n
219226 | JSVariables var [ varIntro ] _ <- node n
220− , JSLiteral "var" <- node var
227+ , JSLiteral "var" <- node var
221228 , JSVarDecl declN (eq : decl) <- node varIntro
222229 , JSIdentifier name <- node declN
223230 , JSLiteral "=" <- node eq
@@ -251,11 +258,11 @@ toModule mids mid top
251258 = ExportsList <$> mapM toExport (filter (not . isSeparator) (map node props))
252259 where
253260 toExport :: Node -> m (ExportType, String, JSNode, [Key])
254− toExport (JSPropertyNameandValue name _ [val] ) =
255− (,,val,[]) <$> exportType (node val)
261+ toExport (JSPropertyNameandValue name _ [val] ) =
262+ (,,val,[]) <$> exportType (node val)
256263 <*> extractLabel (node name)
257− toExport _ = throwError UnsupportedExport
258−
264+ toExport _ = err UnsupportedExport
265+
259266 exportType :: Node -> m ExportType
260267 exportType (JSMemberDot [f] _ _)
261268 | JSIdentifier "$foreign" <- node f
@@ -264,13 +271,13 @@ toModule mids mid top
264271 | JSIdentifier "$foreign" <- node f
265272 = pure ForeignReexport
266273 exportType (JSIdentifier s) = pure (RegularExport s)
267− exportType _ = throwError UnsupportedExport
268−
274+ exportType _ = err UnsupportedExport
275+
269276 extractLabel :: Node -> m String
270277 extractLabel (JSStringLiteral _ nm) = pure nm
271278 extractLabel (JSIdentifier nm) = pure nm
272− extractLabel _ = throwError UnsupportedExport
273−
279+ extractLabel _ = err UnsupportedExport
280+
274281 isSeparator :: Node -> Bool
275282 isSeparator (JSLiteral ",") = True
276283 isSeparator _ = False
@@ -280,9 +287,9 @@ toModule mids mid top
280287compile :: [Module] -> [ModuleIdentifier] -> [Module]
281288compile modules [] = modules
282289compile modules entryPoints = filteredModules
283− where
290+ where
284291 (graph, _, vertexFor) = graphFromEdges verts
285−
292+
286293 -- | The vertex set
287294 verts :: [(ModuleElement, Key, [Key])]
288295 verts = do
@@ -290,12 +297,12 @@ compile modules entryPoints = filteredModules
290297 concatMap (toVertices mid) els
291298 where
292299 -- | Create a set of vertices for a module element.
293− --
300+ --
294301 -- Some special cases worth commenting on:
295302 --
296303 -- 1) Regular exports which simply export their own name do not count as dependencies.
297304 -- Regular exports which rename and reexport an operator do count, however.
298− --
305+ --
299306 -- 2) Require statements don't contribute towards dependencies, since they effectively get
300307 -- inlined wherever they are used inside other module elements.
301308 toVertices :: ModuleIdentifier -> ModuleElement -> [(ModuleElement, Key, [Key])]
@@ -306,18 +313,18 @@ compile modules entryPoints = filteredModules
306313 toVertex (RegularExport nm, nm1, _, ks) | nm /= nm1 = Just (m, (p, nm1), ks)
307314 toVertex _ = Nothing
308315 toVertices _ _ = []
309−
316+
310317 -- | The set of vertices whose connected components we are interested in keeping.
311318 entryPointVertices :: [Vertex]
312319 entryPointVertices = catMaybes $ do
313320 (_, k@(mid, _), _) <- verts
314321 guard $ mid `elem` entryPoints
315322 return (vertexFor k)
316−
323+
317324 -- | The set of vertices reachable from an entry point
318325 reachableSet :: S.Set Vertex
319326 reachableSet = S.fromList (concatMap (reachable graph) entryPointVertices)
320−
327+
321328 filteredModules :: [Module]
322329 filteredModules = map filterUsed modules
323330 where
@@ -326,29 +333,29 @@ compile modules entryPoints = filteredModules
326333 where
327334 go :: [ModuleElement] -> [ModuleElement]
328335 go [] = []
329− go (d : Other semi : rest)
336+ go (d : Other semi : rest)
330337 | JSLiteral ";" <- node semi
331338 , not (isDeclUsed d)
332339 = go rest
333− go (d : rest)
340+ go (d : rest)
334341 | not (isDeclUsed d) = go rest
335342 | otherwise = d : go rest
336−
343+
337344 -- | Filter out the exports for members which aren't used.
338345 filterExports :: ModuleElement -> ModuleElement
339346 filterExports (ExportsList exps) = ExportsList (filter (\(_, nm, _, _) -> isKeyUsed (mid, nm)) exps)
340347 filterExports me = me
341−
348+
342349 isDeclUsed :: ModuleElement -> Bool
343350 isDeclUsed (Member _ _ nm _ _) = isKeyUsed (mid, nm)
344351 isDeclUsed _ = True
345−
352+
346353 isKeyUsed :: Key -> Bool
347− isKeyUsed k
354+ isKeyUsed k
348355 | Just me <- vertexFor k = me `S.member` reachableSet
349356 | otherwise = False
350−
351−-- | Topologically sort the module dependency graph, so that when we generate code, modules can be
357+
358+-- | Topologically sort the module dependency graph, so that when we generate code, modules can be
352359-- defined in the right order.
353360sortModules :: [Module] -> [Module]
354361sortModules modules = map (\v -> case nodeFor v of (n, _, _) -> n) (reverse (topSort graph))
@@ -356,11 +363,11 @@ sortModules modules = map (\v -> case nodeFor v of (n, _, _) -> n) (reverse (top
356363 (graph, nodeFor, _) = graphFromEdges $ do
357364 m@(Module mid els) <- modules
358365 return (m, mid, mapMaybe getKey els)
359−
366+
360367 getKey :: ModuleElement -> Maybe ModuleIdentifier
361− getKey (Require _ _ mi) = Just mi
368+ getKey (Require _ _ mi) = Just mi
362369 getKey _ = Nothing
363−
370+
364371-- | A module is empty if it contains no exported members (in other words,
365372-- if the only things left after dead code elimination are module imports and
366373-- "other" foreign code).
@@ -374,7 +381,7 @@ isModuleEmpty (Module _ els) = all isElementEmpty els
374381 isElementEmpty (Require _ _ _) = True
375382 isElementEmpty (Other _) = True
376383 isElementEmpty _ = False
377−
384+
378385-- | Generate code for a set of modules, including a call to main().
379386--
380387-- Modules get defined on the global PS object, as follows:
@@ -384,10 +391,10 @@ isModuleEmpty (Module _ els) = all isElementEmpty els
384391-- ...
385392-- })(PS["Module.Name"] = PS["Module.Name"] || {});
386393--
387−-- In particular, a module and its foreign imports share the same namespace inside PS.
394+-- In particular, a module and its foreign imports share the same namespace inside PS.
388395-- This saves us from having to generate unique names for a module and its foreign imports,
389396-- and is safe since a module shares a namespace with its foreign imports in PureScript as well
390−-- (so there is no way to have overlaps in code generated by psc-make).
397+-- (so there is no way to have overlaps in code generated by psc).
391398codeGen :: Maybe String -- ^ main module
392399 -> String -- ^ namespace
393400 -> [Module] -- ^ input modules
@@ -399,85 +406,85 @@ codeGen optionsMainModule optionsNamespace ms = renderToString (NN (JSSourceElem
399406 where
400407 declToJS :: ModuleElement -> [JSNode]
401408 declToJS (Member n _ _ _ _) = [n]
402− declToJS (Other n) = [n]
403− declToJS (Require _ nm req) =
404− [ NN (JSVariables (NT (JSLiteral "var") tokenPosnEmpty [ WhiteSpace tokenPosnEmpty "\n " ])
405− [ NN (JSVarDecl (sp (JSIdentifier nm))
409+ declToJS (Other n) = [n]
410+ declToJS (Require _ nm req) =
411+ [ NN (JSVariables (NT (JSLiteral "var") tokenPosnEmpty [ WhiteSpace tokenPosnEmpty "\n " ])
412+ [ NN (JSVarDecl (sp (JSIdentifier nm))
406413 [ sp (JSLiteral "=")
407414 , moduleReference sp (moduleName req)
408415 ])
409416 ]
410417 (nt (JSLiteral ";"))) ]
411418 declToJS (ExportsList exps) = map toExport exps
412−
413− where
419+
420+ where
414421 toExport :: (ExportType, String, JSNode, [Key]) -> JSNode
415422 toExport (_, nm, val, _) =
416− NN (JSExpression [ NN (JSMemberSquare [ NT (JSIdentifier "exports") tokenPosnEmpty [ WhiteSpace tokenPosnEmpty "\n " ] ]
417− (nt (JSLiteral "["))
423+ NN (JSExpression [ NN (JSMemberSquare [ NT (JSIdentifier "exports") tokenPosnEmpty [ WhiteSpace tokenPosnEmpty "\n " ] ]
424+ (nt (JSLiteral "["))
418425 (NN (JSExpression [ nt (JSStringLiteral '"' nm) ]))
419426 (nt (JSLiteral "]")))
420427 , NN (JSOperator (sp (JSLiteral "=")))
421428 , reindent val
422429 , nt (JSLiteral ";")
423430 ])
424−
431+
425432 reindent :: JSNode -> JSNode
426433 reindent (NT n _ _) = sp n
427434 reindent nn = nn
428−
435+
429436 indent :: [JSNode] -> [JSNode]
430437 indent = everywhere (mkT squash)
431438 where
432439 squash (NT n pos ann) = NT n (keepCol pos) (map splat ann)
433440 squash nn = nn
434−
441+
435442 splat (CommentA pos s) = CommentA (keepCol pos) s
436443 splat (WhiteSpace pos w) = WhiteSpace (keepCol pos) w
437444 splat ann = ann
438−
445+
439446 keepCol (TokenPn _ _ c) = TokenPn 0 0 (c + 2)
440−
447+
441448 prelude :: [JSNode]
442− prelude =
449+ prelude =
443450 [ NN (JSVariables (NT (JSLiteral "var") tokenPosnEmpty [ CommentA tokenPosnEmpty ("// Generated by psc-bundle " ++ showVersion Paths.version)
444451 , WhiteSpace tokenPosnEmpty "\n"
445− ])
446− [ NN (JSVarDecl (sp (JSIdentifier optionsNamespace))
452+ ])
453+ [ NN (JSVarDecl (sp (JSIdentifier optionsNamespace))
447454 [ sp (JSLiteral "=")
448− , NN (JSObjectLiteral (sp (JSLiteral "{"))
455+ , NN (JSObjectLiteral (sp (JSLiteral "{"))
449456 []
450457 (sp (JSLiteral "}")))
451458 ])
452− ]
459+ ]
453460 (nt (JSLiteral ";")))
454461 , lf
455462 ]
456−
463+
457464 moduleReference :: (Node -> JSNode) -> String -> JSNode
458− moduleReference f mn =
459− NN (JSMemberSquare [ f (JSIdentifier optionsNamespace) ]
460− (nt (JSLiteral "["))
465+ moduleReference f mn =
466+ NN (JSMemberSquare [ f (JSIdentifier optionsNamespace) ]
467+ (nt (JSLiteral "["))
461468 (NN (JSExpression [ nt (JSStringLiteral '"' mn) ]))
462469 (nt (JSLiteral "]")))
463−
470+
464471 wrap :: String -> [JSNode] -> [JSNode]
465− wrap mn ds =
466− [ NN (JSExpression [ NN (JSExpressionParen (nt (JSLiteral "("))
467− (NN (JSExpression [ NN (JSFunctionExpression (nt (JSLiteral "function"))
468− []
469− (nt (JSLiteral "(") ) [nt (JSIdentifier "exports")] (nt (JSLiteral ")"))
470− (NN (JSBlock [sp (JSLiteral "{")]
472+ wrap mn ds =
473+ [ NN (JSExpression [ NN (JSExpressionParen (nt (JSLiteral "("))
474+ (NN (JSExpression [ NN (JSFunctionExpression (nt (JSLiteral "function"))
475+ []
476+ (nt (JSLiteral "(") ) [nt (JSIdentifier "exports")] (nt (JSLiteral ")"))
477+ (NN (JSBlock [sp (JSLiteral "{")]
471478 (lf : ds)
472479 [nl (JSLiteral "}")])))]))
473480 (nt (JSLiteral ")")))
474− , NN (JSArguments (nt (JSLiteral "("))
481+ , NN (JSArguments (nt (JSLiteral "("))
475482 [ NN (JSExpression [ moduleReference nt mn
476483 , NN (JSOperator (sp (JSLiteral "=")))
477484 , NN (JSExpressionBinary "||"
478− [ moduleReference sp mn ]
479− (sp (JSLiteral "||"))
480− [ emptyObj ])
485+ [ moduleReference sp mn ]
486+ (sp (JSLiteral "||"))
487+ [ emptyObj ])
481488 ])
482489 ]
483490 (nt (JSLiteral ")")))
@@ -487,30 +494,30 @@ codeGen optionsMainModule optionsNamespace ms = renderToString (NN (JSSourceElem
487494 ]
488495 where
489496 emptyObj = NN (JSObjectLiteral (sp (JSLiteral "{")) [] (nt (JSLiteral "}")))
490−
497+
491498 runMain :: String -> [JSNode]
492− runMain mn =
493− [ NN (JSExpression [ NN (JSMemberDot [ NN (JSMemberSquare [ nl (JSIdentifier optionsNamespace) ]
494− (nt (JSLiteral "["))
495− (NN (JSExpression [ nt (JSStringLiteral '"' mn) ]))
499+ runMain mn =
500+ [ NN (JSExpression [ NN (JSMemberDot [ NN (JSMemberSquare [ nl (JSIdentifier optionsNamespace) ]
501+ (nt (JSLiteral "["))
502+ (NN (JSExpression [ nt (JSStringLiteral '"' mn) ]))
496503 (nt (JSLiteral "]")))
497− ]
498− (nt (JSLiteral "."))
504+ ]
505+ (nt (JSLiteral "."))
499506 (nt (JSIdentifier "main")))
500507 , NN (JSArguments (nt (JSLiteral "(")) [] (nt (JSLiteral ")")))
501508 ])
502509 , nt (JSLiteral ";")
503510 ]
504−
511+
505512 nt :: Node -> JSNode
506513 nt n = NT n tokenPosnEmpty []
507−
514+
508515 lf :: JSNode
509516 lf = NT (JSLiteral "") tokenPosnEmpty [ WhiteSpace tokenPosnEmpty "\n" ]
510−
517+
511518 sp :: Node -> JSNode
512519 sp n = NT n tokenPosnEmpty [ WhiteSpace tokenPosnEmpty " " ]
513−
520+
514521 nl :: Node -> JSNode
515522 nl n = NT n tokenPosnEmpty [ WhiteSpace tokenPosnEmpty "\n" ]
516523
@@ -518,21 +525,21 @@ codeGen optionsMainModule optionsNamespace ms = renderToString (NN (JSSourceElem
518525-- This function performs dead code elimination, filters empty modules
519526-- and generates and prints the final Javascript bundle.
520527bundle :: forall m. (Applicative m, MonadError ErrorMessage m)
521− => [(ModuleIdentifier, String)] -- ^ The input modules. Each module should be javascript rendered from 'Language.PureScript.Make' or @psc-make@.
528+ => [(ModuleIdentifier, String)] -- ^ The input modules. Each module should be javascript rendered from 'Language.PureScript.Make' or @psc@.
522529 -> [ModuleIdentifier] -- ^ Entry points. These module identifiers are used as the roots for dead-code elimination
523530 -> Maybe String -- ^ An optional main module.
524531 -> String -- ^ The namespace (e.g. PS).
525532 -> m String
526533bundle inputStrs entryPoints mainModule namespace = do
527534 input <- forM inputStrs $ \(ident, js) -> do
528− ast <- either (const $ throwError UnableToParseModule) pure $ parse js (moduleName ident)
535+ ast <- either (throwError . ErrorInModule ident . UnableToParseModule) pure $ parse js (moduleName ident)
529536 return (ident, ast)
530537
531538 let mids = S.fromList (map (moduleName . fst) input)
532−
539+
533540 modules <- mapM (fmap withDeps . uncurry (toModule mids)) input
534−
541+
535542 let compiled = compile modules entryPoints
536543 sorted = sortModules (filter (not . isModuleEmpty) compiled)
537−
544+
538545 return (codeGen mainModule namespace sorted)