profile
viewpoint
If you are wondering where the data of this site comes from, please visit https://api.github.com/users/conartist6/events. GitMemory does not store any data, but only uses NGINX to cache data for a period of time. The idea behind GitMemory is simply to give users a better reading experience.
Conrad Buck conartist6 Lewisburg, PA burningpotato.com I want to build things that outlive me. Enthusiastic about iteration.

conartist6/babel-plugin-recast 1

Wraps your babel parser in recast

conartist6/errawr 1

Build with better errors! Rawr!

conartist6/acorn 0

A small, fast, JavaScript-based JavaScript parser

conartist6/angular.js 0

HTML enhanced for web apps

conartist6/babel 0

:tropical_fish: Babel is a compiler for writing next generation JavaScript.

conartist6/babel-plugin-handbook 0

How to create Babel plugins

conartist6/babel-plugin-macros 0

🎣 Allows you to build simple compile-time libraries

conartist6/babel-plugin-transform-package-self-reference 0

Transfroms package self-references by reading package.json

conartist6/babylon 0

:page_with_curl: A JavaScript parser

issue commenttapjs/stack-utils

Feature: Support error causes

Completely revamping the representation of stack to make it not-megamorphic and easier to document/extend.

conartist6

comment created time in 18 hours

issue commenttc39/proposal-error-cause

Aggregate Errors have a cause

Be that as it may, I didn't mean for you to read in that deep into the example. I merely hoped you'd see that there's no way to safely catch that error at all for any reason, because you can't tell it apart from a completely different kind of error.

conartist6

comment created time in a day

issue commenttapjs/stack-utils

Feature: Support error causes

Hmm thinking about this some more and I don't like isCause. Mainly the reason we need to understand it is so that Caused by: doesn't show up as part of the message for the next error in the chain.

To generalize that functionality I could:

  • ...make the tool configurable, so that you could feed it a list of string to treat as not-part-of-message.
  • ...split out anything before the first : in a message and store it in a property. The first message for the top stack frame would not do this.

I'm tempted to do the second thing. It would be easier to implement, test, and document, and it does all it needs to do. The purpose of this tool has never been (in my mind) to "understand" errors. Someone could build that on top. This just needs to be able to tweak and reprint.

conartist6

comment created time in 2 days

issue commenttapjs/stack-utils

Feature: Support error causes

The new parseFrame now passes the old test for parseLine. I see that in addition to nested-error-stacks, there is more general support for freetext frames such as

Error:
    at outer (produce-long-stack-traces.js:7:27)
From previous event:
    at Object.<anonymous> (produce-long-stack-traces.js:6:36)

My initial idea was to parse into a structure like this:

type StackFrame = {
    type: 'Frame';
    file: string;
    function?: string;
    // ...
  } | {
    type: 'Text';
    text: string;
  };

type ParsedError =  {
  name: string;
  message: string;
  stack: Array<StackFrame>;
  cause: ParsedError;
};

The weird thing with this design is that causes are from a syntactic perspective a subset of freetext stack frames, yet this structure treats them in completely different ways. The other drawback is that it's actually fairly valuable I think to have the ability to reverse iterate the cause stack, i.e. going from most specific to least specific as error handling does in real code. Essentially chaining cause through a cause property is creating a singly linked list, where an array would more easily permit reverse iteration.

If I do use an array I'd probably need a new type to store it in. I'd have something like:

type ErrorChain = {
  chain: Array<Error>;
};

type Error = {
  name: string;
  message: string;
  stack: Array<StackFrame>;
  isCause: boolean;
};

This could work much better because there'd be no more need for "freetext frames". Instead the freetext would just become the message in a new error, and the stack would always be guaranteed to be an array of frames, which seems useful from the perspective of writing code to filter out spurious frames.

I think the combined advantages of the chain approach are worth making it my new design.

conartist6

comment created time in 2 days

issue commenttapjs/node-tap

Object diffing omits some differences.

That the test prints only the expected object and the diff (but never the actual object) makes this particularly tricky if you don't already understand what you're seeing.

conartist6

comment created time in 2 days

issue openedtapjs/node-tap

Object diffing omits some differences.

I have a test that uses matches to do a shallow comparison between two objects. The objects have the right keys, but multiple values are different than they should be. Only one difference is shown. here is a repro.

As you can see the actual diff output is:

--- expected
+++ actual
@@ -1,4 +1,4 @@
 Object {
   "first": "foobar",
-  "second": "foobar",
+  "second": "bar",
 }

But I expect to see two differences! In fact first is "foo", which is not the expected "foobar". This diff fragment makes it appear that the value of first is correct, when it is not.

created time in 2 days

startedmosjs/mos

started time in 2 days

issue commenttc39/proposal-explicit-resource-management

Consider giving this a new type of error.

I suppose the argument against this is that it would be up to the user to make all their resources' return methods in a custom DispoableError (with a cause wrapping the real issue). Then you could do e instanceof AggregateError && !e.cause && e.errors.every(e => e instanceof DispoableError).

That's a lot though, and won't work well with any library or language feature which attempts to allow Java-style catching of errors by type, something which I think will be more popular once there is a sane and supported way to rethrow certain errors.

conartist6

comment created time in 2 days

issue commenttc39/proposal-explicit-resource-management

Consider giving this a new type of error.

This issue is to track part of an existing discussion here.

conartist6

comment created time in 2 days

issue openedtc39/proposal-explicit-resource-management

Consider giving this a new type of error.

Currently I understand that failures in resource cleanup will be thrown as an AggregateError, which may or may not have a cause property depending on whether or not the block which uses the resources errored (it's not a syntactic block but you get what I mean).

I would like to suggest that there be a ResourceDisposalError or some new type of error, because developers will sometimes want to handle cleanup errors. It may be that I use a particular resource for which I know it is possible for disposal to fail, and I know that I don't care if it does. If so the current spec forces me to write:

try {
  await criticalActionUsingResources()
} catch(e) {
  // Did the critical action complete? Filter out disposal errors for separate handling
  if (e instanceof AggregateError) {
    // But I don't know if this is a resource cleanup error!
    // Maybe it's a failed Promise.all
  }
}

created time in 2 days

issue commenttc39/proposal-error-cause

Aggregate Errors have a cause

I have two words for you: Error: Success. Ah, I miss C.

What I mean is, an error in cleaning up once a task has completed successfully is not the same thing as an error completing a task. To a user, it is not the same. You ran into a problem doing what I asked you to do. OK, thanks for telling me. Did you complete the task? "Yes, but I had some trouble cleaning up" is the answer I expect, but in the system described by these proposals, it isn't possible to tell the difference, unless you treat the presence of cause as a clever flag.

To demonstrate I've modified your example.

const foo = createResourceFoo();
const bar = createResourceBar();

try {
  return Promise.all(useResource(foo), useResource(bar));
} catch (mainProcedureError) {
  // try to clean up the resources.
  const errors = [];
  try {
    foo.dispose();
  } catch (e) {
    errors.push(e);
  }
  try {
    bar.dispose();
  } catch (e) {
    errors.push(e)
  }

  // create an aggregation error with two unrelated errors and a cause describes the context of the aggregated errors.
  throw new AggregateError(errors, '...', { cause: mainProcedureError });
}

I don't think this is some academic debate. There are plenty of times, like when you click a "Purchase" button, when it's pretty important to know whether your action was carried out or not. I understand that in the fail-fast mentality you want to blow up ASAP to help developers no matter what went wrong, but that's not always appropriate. Sometimes code need to be resilient to failures in order to meet the expectations of users. Thus my suggestion would be to create a new ResourceDisposalError with, say, error.resources and leave AggregateError alone.

conartist6

comment created time in 2 days

issue commenttc39/proposal-error-cause

Aggregate Errors have a cause

...especially for library code which cannot make many assumptions beyond compliance with the spec

Actually that's not quite what I meant. Compliance is easy, consistency is harder. If there is consistency, I can build tools that help people who aren't doing anything counterproductive. Using one class to mean two different things is what I'd normally call counterproductive.

conartist6

comment created time in 3 days

issue commenttc39/proposal-error-cause

Aggregate Errors have a cause

The spec doesn't govern how errors are displayed

Of course. But the way the spec is written can certainly determine what is or is not possible, especially for library code which cannot make many assumptions beyond compliance with the spec. The spec is certainly free to introduce a new type of error, as it has in the past. It's never needed to really as far as I can tell, but it's certainly useful. Why not so here?

conartist6

comment created time in 4 days

issue commenttc39/proposal-error-cause

Aggregate Errors have a cause

Ah ok, thanks for sharing the example. That does help a lot in making sense of this. I do not believe it resolves my complaint though. My complaint is, whatever way the language endorses handling errors, for it to help developers and users it must provide a way to answer the question: what went wrong? In essence, what do I need to fix in order to change an unsuccessful outcome to a successful one?

My problem is that aggregate.errors essentially has two different meanings, depending on whether cause is or is not present. If cause is present, the extra errors are likely related to cleanup and are non-causal, i.e. something for the developer to check on but otherwise not something you want to report to the user. If cause is not present then aggregate.errors is the cause which needs to be reported to the user. Do you agree @legendecas?

conartist6

comment created time in 5 days

issue commentkach/nearley

Feature: support syntax to drop value of matched token

A node's type is worth considering since it a sane parser would ensure that the type property was in every node's first slot. It's not a particularly difficult requirement to incorporate into an API though.

conartist6

comment created time in 7 days

issue commentkentcdodds/babel-plugin-macros

Feature: re-entrant macros.

Oh I don't need anything really. I'm just thinking out loud, stating a direction I'd like to head and some reason behind it. If I recall the means for communication with macros evaluators is already there. I've fed boolean variables into macros and I don't see why I couldn't feed in a configured callback.

conartist6

comment created time in 8 days

issue openedkentcdodds/babel-plugin-macros

Feature: re-entrant macros.

I have a feature to run by you @kentcdodds. As I'm sure I've mentioned I've been slowly working towards v1 of my file watching build tool, macrome. I had stepped away from work on it for a while and as I've started thinking more about the final product over the past few days I've realized that I'm particularly excited about an integration that is possible with babel-plugin-macros (which I shall detail below).

Macrome is a really barebones build system. In essence it's two parts:

    1. A watcher. It is a rich layer built on top of facebook's watchman. It is aware of version control operations and allows you to react to changes on the disk.
    1. A tracking engine. When macrome writes files it tags them with an /* @macrome */ leading comment. It knows how to efficiently track down files with its headers, enabling it to efficiently locate and remove stale content.

The purpose of these two parts is to enable development workflows that make the code the source of truth, and particularly aim to unhide a lot of magic that has snuck its way into how modern frontend development is done. By magic I'm talking about things like experimental language features, JSX, CSS modules, macros, and other things. So my functionality is to take a file that has JSX or a babel macro in it, and spit out a copy of the real code on disk, most likely co-located with the original file. The theory is it's the plain javascript that's your product, and that you should be able to use as much magic as you want yet still understand what the output is, i.e. what code the client will be running. It's super useful to have access to that before the structure of it is all erased because it will be an invaluable asset for static analysis and for new developers, both of whom likely know about the basic rules of javascript but not every crazy thing that developers have invented.

OK but I'm not even to the proposal yet! Sorry. All that is old hat. It's more or less built, and it would be crazy useful even as just that. But I think I have an even better idea: create explicit support for overwriting input with output, i.e. doing codemods. This is something I've considered for a while, but it was only recently that I realized just how useful it could be when coupled with macros and the power of the watching engine.

TL;DR Let me describe the simplest re-entrant macro. Its purpose is to tag certain sites in code that integrate with outside tooling. Translations will be a very common example of. Errors should be also. These are little pieces of the code that also will need to be in a database somewhere, but you've got a problem: when the code changes, how can you tell which snippets are the same? You need to either manually create and track IDs or to treat some part of your data as a key, which has its own drawbacks. Enter the new way:

// translatable.js
import { id} from 'babel-plugin-macros';

export default translate(id, 'The quick brown fox jumped over the lazy dog');

And the magic is when you save the file, it overwrites itself. This is why the macro code must be re-entrant, it can't send itself into an infinite loop. What you should get is: // translatable.js import { id} from 'babel-plugin-macros';

export default translate(id15, 'The quick brown fox jumped over the lazy dog');

And now hopefully you see why macros needs an integration with some bigger central system. Unless it is always generation UUIDv4s, this macro needs a map somewhere that tracks which IDs it has given out. Macrome can provide that central location. Of course you could always have had a central database anyway, but then you get into some trickiness when developing your code. When do you create the database entry? How do you avoid conflicts between developers? The biggest companies have solutions to these problems, and most everyone else just stays away. Incidentally, how do you avoid conflicts between developers? I'm not sure of the specifics, but I'm sure you can do it with more macros. You just need some way to flag to CI that it needs to generate a new value. The obvious way would be to commit with your `id` macro usage not expanded to a template string, but if macrome is watching it would just reexpand it. Instead you could do something like `` id.dev`15` ``. The macro would then have different behavior on CI: behaving as if no id had yet been provided and also rewriting itself to import the `id` macro in place of `id.dev`. This is the power of having the code itself be the source of truth.

Finally though it doesn't so much involve macros, imagine the power of a re-entrant code generator for documentation. Let's say you have your `README.md` file. You tell macrome to process it with a re-entrant embedding generator. Now you can write this:

Usage

\\ ./tests/basic-usage.test.js

And once you save the file macrome will go and dig up the real content of that file and paste it in there for you. Now you have documentation that is also test code! This implies some things I have to think further about though in terms of how macrome might allow generators to specify dependencies, i.e. I need to be rebuilt if one of my dependencies does. Fortunately I think I already need such a system to support watching configs, as I want to be able to create a tool that is not prone to needing to be restarted.

created time in 8 days

push eventconartist6/errawr

Conrad Buck

commit sha a218b298cf71da781052507b9f341a45b1f150d8

Fix invariant types

view details

push time in 8 days

issue commentmicrosoft/TypeScript

Assertion functions don't work when defined as methods

Note that if you need method overloading semantics your workaround will need to create an interface:

interface InvariantStatic {
  (condition: false, reason: string): never;
  (condition: any, reason: string): asserts condition;
}

declare let invariant: InvariantStatic;
mlhaufe

comment created time in 8 days

issue commentkach/nearley

Feature: support syntax to drop value of matched token

The difference if you put the string keys and the indexed keys on the same object is that now it's useless as an AST node -- you need some custom code to copy the part you actually want into a new allocation, which is just work that doesn't need to be done. If the parser itself can build your AST nodes and ever postprocessor can be one of a few functions defined centrally you'll have a physically smaller parser with fewer paths which churns less memory and gets fully optimized more quickly by the runtime.

conartist6

comment created time in 9 days

issue commentkach/nearley

Feature: support syntax to drop value of matched token

Yes I'm proposing a breaking change -- just shifting the current optional callback args right. There doesn't seem to be any reason not to propose a breaking solution as likely it could only happen in a fork.

conartist6

comment created time in 9 days

issue commentkach/nearley

Feature: support syntax to drop value of matched token

Yeah named entries would be great, and even better if they were passed to the postprocessor in a separate argument. If the callback became (tuple, dict, ...args) => value it would be trivial to write function dict(tuple, dict) { return dict; }. In a lot of common cases this would eliminate the need for custom postprocessing.

conartist6

comment created time in 9 days

push eventconartist6/stack-utils

Conrad Buck

commit sha d2ee3192952722f60e6dece6193eb57ddead6a53

wip

view details

push time in 9 days

issue commentkach/nearley

Feature: support syntax to drop value of matched token

Oh somehow the first part of your message got italicized and I thought it was a quote. I've already separated this code out into two parsers so that I can prune branches on a line-by-line basis. I'll definitely check out your combinators package when it's ready.

conartist6

comment created time in 9 days

issue commentkach/nearley

Feature: support syntax to drop value of matched token

Ah right, I'll update that. __s sure would make it significantly more readable.

conartist6

comment created time in 9 days

issue commentkach/nearley

Feature: support syntax to drop value of matched token

That's a stylistic choice, and it certainly may be a valid one. I actually did break things down more as part of the natural development of my parser code (the initial example is called CallSite now), but now there's just a different big nasty line that doesn't break down.

Call "(" _:? "eval" _ "at" _ Text _ "(" _:? Site _:? ")" ( "," _:? Site ):? _:? ")"

I could deconstruct it down arbitrarily, but since the conceptual breakdown is already complete I'd rather not.

I'm also not convinced that any of these other parsers are going to hack it for my use case. This grammar is fundamentally ambiguous, and there's nothing I can do about it since I didn't come up with the grammar.

Of course I could always for nearley, but I've been on a forking spree lately and I need to settle down and bring one set of projects to completion.

conartist6

comment created time in 9 days

startedkach/nearley

started time in 9 days

issue commentkach/nearley

Feature: support syntax to drop value of matched token

I am aware of that feature of destructuring, but as you note it doesn't solve my problem -- merely moves it somewhere else. I think your caveat is particularly telling -- that though you did exactly the work you suggest, neither of us is sure if it's right. The library could provide a better solution.

conartist6

comment created time in 9 days

issue commenttapjs/stack-utils

Feature: Support error causes

I'm happy to report that it's all coming together. My new implementation now supports all the syntax that this parser supports as well as some new things like async function prefixes and <anonymous> as a location (anonymous generators output this). Working on getting the test suite to pass, and I need the to consider how to present the differences between paths and URIs.

conartist6

comment created time in 10 days

push eventconartist6/stack-utils

Conrad Buck

commit sha 7afc991aba5f8caa927d1c587f38758d6957e824

wip

view details

push time in 10 days