{"action":"created","comment":{"url":"https://api.github.com/repos/dart-lang/language/pulls/comments/233684660","pull_request_review_id":175157192,"id":233684660,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIzMzY4NDY2MA==","diff_hunk":"@@ -0,0 +1,1665 @@\n+# Terminating Tokens\n+\n+Make Dart cleaner and less error-prone by eliminating pointless semicolon\n+syntax, while avoiding the insanity of JavaScript's semicolon insertion rules.\n+\n+## Motivation\n+\n+For such a tiny feature&mdash;literally just eliminating some `;` at the ends of\n+some lines&mdash;people have *really* strong feelings about using newlines as\n+statement separators. Because of that, 2/3 of this proposal is about persuading\n+you that we *should* and *can* do it. If you already think optional semicolons\n+is a good idea, feel free to skip the next two sections and go straight to the\n+proposal. Otherwise, I'll do my best to convince you first.\n+\n+### User desire\n+\n+The simplest motivation is that many users already want it. It was one of the\n+[very first feature requests for Dart][sdk#34], and has 20 👍 and 3 ❤️. (That's\n+not a lot, but keep in mind this bug was closed years ago.) The [more recent\n+issue][sdk#30347] has 40 👍, 18 🎉, and 11 ❤️, compared to 7 😕 and 2 👎.\n+\n+[sdk#34]: https://github.com/dart-lang/sdk/issues/34\n+[sdk#30347]: https://github.com/dart-lang/sdk/issues/30347\n+\n+BASIC, F#, Go, Groovy, Haskell, JavaScript, Julia, Lua, Python, R, Ruby, Scala,\n+and the various shell languages all omit semicolons. Dart's main competitors are\n+TypeScript, Swift, and Kotlin. They all do too.\n+\n+The odds that all of the Dart users who starred those bugs, the designers of\n+those other languages, and their users are all delusional is pretty slim. This\n+may not be enough to convince you that significant newlines are *better*, since\n+there are also plenty of successful languages that *don't* have them, but it\n+should send a clear signal that they are a reasonable idea.\n+\n+### User errors\n+\n+One aspect of good syntax design is that it minimizes user error. Humans are\n+fallible no matter what, but a good syntax has fewer bumps in the carpet to trip\n+over. Those of us well-versed in semicolons are so used to writing them that we\n+probably forgot how *un*-natural they were at first. That bump is still there,\n+we've just learned to sidestep it. New Dart users, though, still have to learn\n+that. Eliminating them can make that process easier.\n+\n+**TODO: Are there any studies on this we can reference?**\n+\n+Even experienced manual semicolon inserters still make mistakes. After years of\n+programming in Dart, I still forget the mandatory semicolon when assigning a\n+lambda to a variable:\n+\n+```dart\n+someCallback = () {\n+  ...\n+} // <-- Error!\n+```\n+\n+Flutter code, with its deep nesting of expressions and lambdas exacerbates this. Look at the series of closing delimiters at the end of:\n+\n+```dart\n+Widget build(BuildContext context) {\n+  return CupertinoTabScaffold(\n+    tabBuilder: (context, int index) {\n+      return CupertinoTabView(\n+        builder: (context) {\n+          return CupertinoPageScaffold(\n+            navigationBar: CupertinoNavigationBar(\n+              middle: Text('Page 1 of tab $index'),\n+            ),\n+            child: Center(\n+              child: CupertinoButton(\n+                child: const Text('Next page'),\n+                onPressed: () {\n+                  Navigator.of(context).push(\n+                    CupertinoPageRoute<Null>(\n+                      builder: (context) {\n+                        return CupertinoPageScaffold(\n+                          navigationBar: CupertinoNavigationBar(\n+                            middle: Text('Page 2 of tab $index'),\n+                          ),\n+                          child: Center(\n+                            child: CupertinoButton(\n+                              child: const Text('Back'),\n+                              onPressed: () { Navigator.of(context).pop(); },\n+                            ),\n+                          ),\n+                        );\n+                      },\n+                    ),\n+                  );\n+                },\n+              ),\n+            ),\n+          );\n+        },\n+      );\n+    },\n+  );\n+}\n+```\n+\n+The commas are optional, but each of those five semicolons is required. Omit\n+one, and it's an error. Place one on any of the other lines where they aren't\n+required, and it's an error.\n+\n+The fact that it's an *error* to omit them shows how needless they are in many\n+places. The language *already knows* the statement has ended, which is why it's\n+able to tell you you forget the semicolon at exactly that point. The semicolons\n+tell it nothing it doesn't already know.\n+\n+### Language commitment\n+\n+Fair warning, this gets kind of squishy.\n+\n+Choosing a language is a big commitment. When a user chooses to write a large\n+program in Dart, they are stuck with it for a long period of time. To make that\n+kind of commitment, they want assurance not just that Dart is good today but\n+that its stewards are taking it in the direction they want. They are looking for\n+signals about what kind of principles or philosophy the shepherds of the\n+language have so they can predict if the language will continue to be a good fit\n+for them.\n+\n+A good example of this is [tail call optimization][tco]. Every time a new\n+language appears on the scene, people show up asking if it does \"proper tail\n+calls\". (In Dart, it was [issue #29][sdk#29].) TCO is a neat feature that can\n+enable some nice implementation patterns. But, if your language has loops (which\n+they almost all do), it doesn't let you do anything you can't already do. It's a\n+minor control flow feature, like a switch statement.\n+\n+But people get *really* into this. Here's [a well-known, heated thread about\n+supporting it in Rust][rust tco]. Buckets of ink have been spilled discussing it\n+for JavaScript. Why do they get so emotionally invested in it? I believe it's\n+because they are asking if the language is part of the \"functional\" camp. Will\n+future versions of the language add new features that encourage functional\n+styles (JS, probably yes) or not (Python, probably no)?\n+\n+This does actually matter, because if future versions of a language invest in\n+features you don't want, you have to deal with the migration and the opportunity\n+cost of features you *could* have had.\n+\n+[tco]: https://en.wikipedia.org/wiki/Tail_call\n+[sdk#29]: https://github.com/dart-lang/sdk/issues/29\n+[rust tco]: https://mail.mozilla.org/pipermail/rust-dev/2013-April/003557.html\n+\n+Other camps form around how explicitly the user must express their intent to the\n+computer versus the computer filling in the blanks on their behalf. On one side\n+are the dynamically-typed scripting languages where the implementation does its\n+best to take the user's code and go off and run it. If the user wrote something\n+wrong, it's not discovered until the last possible minute when an operation\n+can't be performed.\n+\n+On the other end are very explicitly-typed languages like Java, Ada, and C++.\n+Those require you to laboriously prove to the compiler that you know what your\n+code is doing. In this:\n+\n+```c\n+int i = 3;\n+```\n+\n+You are telling the compiler that `i` is `3`, and you're also telling it that\n+`i` is an integer. The compiler already knows that, but it needs to know that\n+*you* know it too. Either it doesn't trust you, or you don't trust it to figure\n+that out.\n+\n+Newer languages stake out a middle position: the user states their intent once,\n+briefly. The machine figures out what they mean. If unclear, it requires\n+clarification instead of just trying to muddle through.\n+\n+Type inference is the canonical example. If it can figure out the static type of\n+the code for you, it does. Once it has, it reflects that *back* to you in terms\n+of static errors so that you can *see* what it figured out before you run it.\n+\n+Dart, especially after Dart 2, is in that camp. I can't say it better than [Seth\n+Ladd][seth]:\n+\n+[seth]: https://github.com/dart-lang/sdk/issues/30347#issuecomment-321058277\n+\n+> Modern languages are generally believed to be less boilerplate, more terse,\n+> and include language features working for you. For example, type inferencing\n+> is the language working for you. We could force you to write type annotations\n+> everywhere, or we can say \"in many cases, we can infer it for you\".\n+> Auto-inserting punctuation is another way the language can work for you. Also,\n+> there's an element of being seen as a light-weight, modern language and\n+> staying competitive. The trend in languages was to go to `var`, to go for\n+> closures, to go for `=>` functions, and now to go for implicit semicolons\n+> (done right, of course).\n+\n+When users ask us to support optional semicolons, part of that is a question:\n+\"Is that the kind of language you are?\" I believe Dart already *is* that kind of\n+language. With type inference, optional `new` and `const`, unprefixed imports,\n+implicit `this`, tear-offs, and other features, the language is clearly\n+well-established in the camp of languages that don't require unnecessary\n+verbiage.\n+\n+From that angle, supporting optional semicolons is an *accurate* and *useful*\n+signal for us to send.\n+\n+### What \"optional\" means\n+\n+One last point. I know some are less concerned about making newlines significant\n+than they are with doing that *and* still allowing semicolons. For very good\n+reasons, they don't want a bifurcation in the ecosystem where half the Dart\n+world says \"You need to write semicolons!\" and the other half says \"No\n+semicolons ever!\"\n+\n+This is a valid concern. This proposal *does* allow both explicit and inferred\n+semicolons. The intent is *not* to allow users to choose their preferred style.\n+If this proposal ships, the [official style guide][] will tell you to omit\n+semicolons. [Dartfmt][], the canonical automatted formatter for Dart code will\n+remove them.\n+\n+[official style guide]: https://www.dartlang.org/guides/language/effective-dart\n+[dartfmt]: https://github.com/dart-lang/dart_style\n+\n+We still allow semicolons for three reasons:\n+\n+*   **To avoid massively breaking existing Dart code which has them.** Making\n+    semicolons an error would break every single Dart program in the world when\n+    users upgraded to the latest SDK. That's bad.\n+\n+    It would make it harder to write migration tools that remove those\n+    semicolons, since those tools would then need to be able to parse some\n+    pseudo-Dart language that is the superposition of Dart-with-semicolons and\n+    Dart-without-semicolons. In other words, we need to implement a parser that\n+    supports both semicolons and significant newlines no matter what.\n+\n+*   **As an input format for dartfmt and other IDEs.** Many users have strong\n+    muscle memory for writing those `;`. If you have your editor set to\n+    auto-format on save, you can keep doing that and it will strip them back out\n+    for you. Eventually, that muscle memory will fade. But in the meantime, it\n+    avoids forcing you to manually delete them every time you accidentally hit\n+    the `;` key.\n+\n+*   **As an output format for code generators.** The Dart ecosystem uses code\n+    generation pretty heavily. It's often easier for tools that output Dart\n+    source code to be able to do so using semicolons without worrying about\n+    whitespace or formatting. Those tools can then either run the output through\n+    dartfmt to remove the semicolons or just leave them since they're harmless.\n+\n+OK, that's what I have to try to persuade you that optional semicolon are an\n+idea worth *wanting*. The next question is whether it's an *attainable* goal.\n+JavaScript is a good example of the fact that optional semicolons can be done,\n+but done so poorly as to be an anti-feature.\n+\n+Also, each language has its own pecularities, so a good optional semicolon\n+system in one language may not work in another. In other words, we need to not\n+just specify the optional semicolon rules, but *evaluate* them in the context\n+Dart's own grammar and ecosystem.\n+\n+## Evaluation\n+\n+Given some set of rules for eliminating semicolons, how can we tell if they are\n+*good* rules? I measure it along a couple of axes. We want to maximize:\n+\n+*   **Unambiguity** - Removing mandatory semicolons as terminators can lead to\n+    ambiguous cases where both ignoring a newline or treating it as a semicolon\n+    lead to valid parses. Any syntax *must* be unambiguous&mdash;users don't\n+    like it if their code is parsed differently on different days of the week.\n+    When given an ambiguous choice, it must resolve it in a way that aligns with\n+    the user's intention as often as possible.\n+\n+*   **Compatibility** - How much existing code (with explicit semicolons and\n+    whatever whitespace and newlines it happens to have) parses the same way\n+    with the new newline-sensitive grammar as it did with the old grammar? Any\n+    difference here is existing code that is broken by the new rules.\n+\n+*   **Accuracy** - When users write code without semicolons and with newlines\n+    wherever they feel they look best, how often does the parser see the code\n+    they way the user wants it to? How often do users have to fight the grammar\n+    and tweak newlines to satisfy the parser?\n+\n+*   **Robustness** - Given a chunk of code where the newlines are *not*\n+    deliberate, how likely is it to be parsed the way the user wants? If someone\n+    is just banging code into their editor and not thinking about\n+    formatting&mdash;that's dartfmt's job after all&mdash;how likely are they to\n+    get what they want out? If they copy and paste a big chunk of code somewhere else, how often does it end up still correct?\n+\n+In an ideal world, we'd hit 100% on all four of those. Short of radically\n+changing the language into something like Lisp, that's impossible. But we can\n+run experiments on a corpus of Dart code to try to empirically measure these to\n+some degree.\n+\n+The corpus I used is the Flutter repository, the Dart SDK repository, and the\n+1,000 most-recently published packages to pub.dartlang.org. It contains\n+**4,963,739 lines of Dart code** across **19,856 files**. I ignore results from\n+a few language/compiler test files that deliberately test syntax errors or\n+contain comments in odd locations required by the test framework.\n+\n+There are **1,677,054** semicolons that would be removed by this proposal.\n+\n+In order to test the proposal, I hacked together a prototype implementation of\n+its parsing rules in [a fork of the front_end][front_end] package's Fasta\n+parser. I'm not very familiar with Fasta and I ran into a few places where it\n+was hard to correctly *ignore* newlines, so there are a few false positive diffs\n+that I'll call out below.\n+\n+[front_end]: https://github.com/munificent/ui-as-code/tree/master/code/front_end_semicolon\n+\n+### Ambiguity\n+\n+In most places in the grammar, we know a newline is *not* significant because a\n+semicolon can't appear there. For example, we can always safely the ignore\n+newline after `class`:\n+\n+```dart\n+class\n+  Foo {}\n+```\n+\n+In other places, we know the newline *must* be significant because ignoring it\n+leads to an error, as in:\n+\n+```dart\n+main(List<String> arguments) {\n+  print(\"Hello\");\n+  if (arguments.isNotEmpty) print(\"Passed $arguments\");\n+}\n+```\n+\n+Here, we know the newline before `if` *must* be significant because ignoring it\n+leads to an error on the `if`.\n+\n+Now, consider:\n+\n+```dart\n+main() {\n+  foo\n+  (bar)\n+}\n+```\n+\n+This could either be parsed as an expression statement `foo;` followed by an\n+expression statement `(bar);`. Or it could be parsed as an invocation of\n+`foo(bar);`. We have a choice as to whether to treat the newline after `foo` as\n+significant or not. Either option leads to a valid program, but only one is what\n+the user intends.\n+\n+Any optional semicolon system has to resolve these ambiguities and do it in a\n+way that agrees with the user's intention. This is why optional semicolons can\n+be difficult. Most of these ambiguities center around *expression statements*.\n+That's the corner of the language where the rich expression grammar comes into\n+play, and where a semicolon is often necessary to separate two expression\n+statements.\n+\n+The rules for resolving ambiguities need to be simple enough for users to\n+internalize, while also being what the user wants. It turns out there is a\n+simple rule that does what users want most of the time. I call it the \"eagerness\n+principle\". It says, **if there is a valid way to treat a newline as\n+significant, it *should* be significant.**\n+\n+The intuition is that if the user chose to put a newline there, the language\n+should respect that choice and consider it a signal of intent unless it clearly\n+can't be. Eagerness implies that the newline before `(` in the example is\n+significant, so the language parses it like:\n+\n+```dart\n+main() {\n+  foo;\n+  (bar);\n+}\n+```\n+\n+(Note that this is the exact opposite of JavaScript. JS says that newlines do\n+*not* signal user intent at all... unless that leads to ambiguity in which case\n+it circles back and reluctantly takes them into account.)\n+\n+#### Hanging returns\n+\n+This:\n+\n+```dart\n+return\n+123\n+```\n+\n+could be parsed as:\n+\n+```dart\n+return 123;\n+```\n+\n+or:\n+\n+```dart\n+return;\n+123;\n+```\n+\n+The latter is obviously not useful since it causes unreachable code, but it's\n+structurally similar to this useful code:\n+\n+```dart\n+if (condition) return\n+print(\"didn't return\")\n+```\n+\n+It would be bad to ignore the newline after `return` in this last example. So,\n+based on that, and following the eagerness principle, we treat the newline as a\n+semicolon after `return` in all cases. This is how Go and Kotlin behave. Swift\n+ignores the newline, but also requires braces around all control flow\n+statements.\n+\n+This is technically a breaking change to existing return statements that have a\n+newline before their value. How breaking of a change is it? The corpus contains:\n+\n+*   **158,407** return statements.\n+*   **151,363** (95.553%) have a value that starts on the same line as the\n+    `return`.\n+*   **6,949** (4.387%) do not return a value.\n+*   **95** (0.060%) have a value that starts on the next line.\n+*   **81** (0.051%) of those come from a single, um, *idiosyncratically*\n+    formatted package.\n+*   **14** (0.008%) come from other files.\n+\n+This isn't entirely surprising since dartfmt will never place the return value\n+on the next line and most users use the formatter.\n+\n+In cases where a newline is treated as significant and the user doesn't intend\n+that, the resulting code almost always ends up with some static warning or\n+error. You'll usually end up with unreachable code. If not that, you'll\n+typically get a \"missing return value\" error because the function's return type\n+is not void, but the value is treated as not part of the return.\n+\n+#### Empty statements in control flow\n+\n+Consider:\n+\n+```dart\n+for (var i = 0; i < 10; i++)\n+print(\"hi\")\n+```\n+\n+Does this print \"hi\" once or ten times? Does your answer change if that\n+`print()` were to be indented? Dart supports the empty statement, a semicolon\n+all by itself, so the latter is possible if it parses the above as:\n+\n+```dart\n+for (var i = 0; i < 10; i++);\n+print(\"hi\");\n+```\n+\n+We don't want to make *indentation* significant when it comes to semicolons.\n+That tends to make the grammar very sensitive to whitespace mistakes and brittle\n+in unpleasant ways when users do things like copy and paste code.\n+\n+In the corpus, there are:\n+\n+*   **216,865** control flow statements (await-for, do, else, for, for-in, if,\n+    and while).\n+\n+*   **210,746** (97.178%) have a body that isn't an empty statement and starts\n+    on the same line as the control flow header, as in:\n+\n+    ```dart\n+    if (condition) { // <-- block starts on same line\n+      body();\n+    }\n+    ```\n+\n+*   **6,102** (2.814%) have a non-empty body starting on the next line as in:\n+\n+    ```dart\n+    if (condition)\n+      body();\n+    ```\n+\n+    It's worth noting this [violates the official style guide][style guide].\n+    Even so, the formatter will produce this formatting if the user didn't write\n+    braces and the body doesn't fit on one line. More than half of these are in\n+    the Flutter repository, which doesn't use dartfmt or fully follow the style\n+    guide.\n+\n+*   **17** (0.008%) have bodies that are empty statements.\n+\n+[style guide]: https://www.dartlang.org/guides/language/effective-dart/style#do-use-curly-braces-for-all-flow-control-structures\n+\n+It's rare that a user deliberately wants an empty statement as the body of a\n+control flow statement, so we resolve this by disallowing that completely. If\n+the user wants an empty body (typically a loop where the condition expression\n+has a side effect), they can use an empty block, which is usually more readable\n+anyway.\n+\n+Without empty statements, we know that a newline inside a control flow statement\n+(after the if and while condition, after `else`, etc.) *cannot* be meaningful.\n+Thus, we can safely ignore it, which means that this:\n+\n+```dart\n+if (condition)\n+  body();\n+```\n+\n+Continues to be parsed like:\n+\n+```dart\n+if (condition) body();\n+```\n+\n+Just like it is today. That means the 6,102 existing cases like this are not\n+broken, at the expense of having to fix the 17 uses of empty statements.\n+\n+**TODO: The prototype doesn't currently implement this restriction, but there\n+are only a handful of cases where it occurs.**\n+\n+#### Prefix and infix operators\n+\n+Dart has a couple of operators that can appear both at the beginning and middle\n+of an expression. That leads to ambiguity if a newline appears before that\n+operator, as in:\n+\n+```dart\n+a\n+-b\n+```\n+\n+That could be parsed as either:\n+\n+```dart\n+a;\n+-b;\n+```\n+\n+or:\n+\n+```dart\n+a - b;\n+```\n+\n+\n+\n+In all cases, we follow the eagerness principle and treat a newline as\n+significant before these operators. That means one of these operators at the\n+beginning of a line is treated like a unary operator if possible.\n+\n+Note that these are *only* a problem at the \"top level\" of a statement\n+expression. Once the operator is nested inside parentheses or an argument list\n+or something, a semicolon is never allowed and we know we can ignore the\n+newline.\n+\n+The set of operators affected are:\n+\n+*   `-`, which can be used for both unary negation and infix subtraction:\n+\n+    ```dart\n+    -negate\n+    sub - tract\n+    ```\n+\n+    There are **10,551** infix `-` operators in the corpus and **13** (0.123%)\n+    are at the beginning of a line. Those thirteen are all in an expression\n+    context where we know the newline cannot be significant, so are OK too.\n+\n+*   `<`, which can be used for generic collection literals and comparison:\n+\n+    ```dart\n+    <int>[]\n+    less < than\n+    ```\n+\n+    There are **26,049** infix `<` operators and none at the beginning of a\n+    line.\n+\n+*   `(`, which can be a parenthesized expression or a function invocation:\n+\n+    ```dart\n+    (parenthesized)\n+    function(call)\n+    ```\n+\n+    There are **1,437,185** invocations in the corpus, none of which have the\n+    `(` on the next line.\n+\n+*   `[`, which can be a list literal or an index operator:\n+\n+    ```dart\n+    [list, literal]\n+    list[access]\n+    ```\n+\n+    There are **173,429** index operator calls in the corpus, **2** (0.001%) of\n+    which have the `[` on the next line. Both are the result of chained index\n+    operators like:\n+\n+    ```dart\n+    exifData[tag] = ExifConstants.stringValues[\"Components\"]\n+            [exifData[tag][0]]\n+    ```\n+\n+    One is in an expression context where the newline is ignored and one is not.\n+\n+#### Adjacent strings\n+\n+Adjacent strings are an odd corner of the language. They were added during a\n+period of time when Dart disallowed `+` on strings. Now that it supports that,\n+there is little reason to also support adjacent strings, but the language still\n+does and using it is idiomatic.\n+\n+This leads to obvious ambiguity. This:\n+\n+```dart\n+\"one\"\n+\"two\"\n+```\n+\n+Could be:\n+\n+```dart\n+\"one\";\n+\"two\";\n+```\n+\n+or:\n+\n+```dart\n+\"one\" \"two\";\n+```\n+\n+This is the one case where eagerness is obviously wrong. The former\n+interpretation can't accomplish anything useful. And, in fact, newlines are the\n+primary use case for splitting a string literal into multiple adjacent strings:\n+\n+*   The corpus has **11,256** adjacent strings. By that, I mean strings that\n+    immediately follow another string literal, so `\"a\" \"b\" \"c\"` is two adjacent\n+    strings.\n+\n+*   **11,222** (99.698%) have a newline between them and the previous string.\n+\n+So, in this case, we don't follow eagerness and ignore a newline between\n+adjacent strings.","path":"working/terminating-tokens/feature-specification.md","position":626,"original_position":626,"commit_id":"e99098bedc158a1d07117d2e81bdd52a632c492c","original_commit_id":"e99098bedc158a1d07117d2e81bdd52a632c492c","user":{"login":"munificent","id":46275,"node_id":"MDQ6VXNlcjQ2Mjc1","avatar_url":"https://avatars0.githubusercontent.com/u/46275?v=4","gravatar_id":"","url":"https://api.github.com/users/munificent","html_url":"https://github.com/munificent","followers_url":"https://api.github.com/users/munificent/followers","following_url":"https://api.github.com/users/munificent/following{/other_user}","gists_url":"https://api.github.com/users/munificent/gists{/gist_id}","starred_url":"https://api.github.com/users/munificent/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/munificent/subscriptions","organizations_url":"https://api.github.com/users/munificent/orgs","repos_url":"https://api.github.com/users/munificent/repos","events_url":"https://api.github.com/users/munificent/events{/privacy}","received_events_url":"https://api.github.com/users/munificent/received_events","type":"User","site_admin":false},"body":"Oof, yes. The proposal will elide the newline there.\r\n\r\n> What can the user do to avoid this? Parenthesizing either string literal.\r\n\r\nYeah, parenthesizing would work.\r\n\r\n> (Let's do that no matter what! Please!)\r\n\r\nSGTM. Empty statement is weird.","created_at":"2018-11-15T01:30:03Z","updated_at":"2018-11-15T01:30:03Z","html_url":"https://github.com/dart-lang/language/pull/73#discussion_r233684660","pull_request_url":"https://api.github.com/repos/dart-lang/language/pulls/73","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/dart-lang/language/pulls/comments/233684660"},"html":{"href":"https://github.com/dart-lang/language/pull/73#discussion_r233684660"},"pull_request":{"href":"https://api.github.com/repos/dart-lang/language/pulls/73"}},"in_reply_to_id":232640393},"pull_request":{"url":"https://api.github.com/repos/dart-lang/language/pulls/73","id":227414942,"node_id":"MDExOlB1bGxSZXF1ZXN0MjI3NDE0OTQy","html_url":"https://github.com/dart-lang/language/pull/73","diff_url":"https://github.com/dart-lang/language/pull/73.diff","patch_url":"https://github.com/dart-lang/language/pull/73.patch","issue_url":"https://api.github.com/repos/dart-lang/language/issues/73","number":73,"state":"open","locked":false,"title":"Add working spec for \"terminating tokens\".","user":{"login":"munificent","id":46275,"node_id":"MDQ6VXNlcjQ2Mjc1","avatar_url":"https://avatars0.githubusercontent.com/u/46275?v=4","gravatar_id":"","url":"https://api.github.com/users/munificent","html_url":"https://github.com/munificent","followers_url":"https://api.github.com/users/munificent/followers","following_url":"https://api.github.com/users/munificent/following{/other_user}","gists_url":"https://api.github.com/users/munificent/gists{/gist_id}","starred_url":"https://api.github.com/users/munificent/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/munificent/subscriptions","organizations_url":"https://api.github.com/users/munificent/orgs","repos_url":"https://api.github.com/users/munificent/repos","events_url":"https://api.github.com/users/munificent/events{/privacy}","received_events_url":"https://api.github.com/users/munificent/received_events","type":"User","site_admin":false},"body":"This is the maximally compatible robust approach to optional semicolons.\r\nI'll start working on a simpler more conservative alternative soon.\r\n\r\nThis PR is just the proposal doc. I have a bunch of other supplementary material:\r\n\r\n* A handful of small scripts for scraping a corpus and counting occurrences of certain forms.\r\n\r\n* A fork of the front_end and dart_style packages with a prototype implementation of the rules.\r\n\r\n* A Makefile for running tests on a big corpus of code.\r\n\r\nThe scripts reuse some existing helper code I wrote for other ui-as-code language features, so I'd have to duplicate some stuff to migrate it all into here. The front_end and dart_style packages are fairly large. Do you think it's worth bringing this stuff in?","created_at":"2018-10-31T19:30:37Z","updated_at":"2018-11-15T01:30:03Z","closed_at":null,"merged_at":null,"merge_commit_sha":"f49ded34a47692d95ea91b81802cd5e3e175367a","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/dart-lang/language/pulls/73/commits","review_comments_url":"https://api.github.com/repos/dart-lang/language/pulls/73/comments","review_comment_url":"https://api.github.com/repos/dart-lang/language/pulls/comments{/number}","comments_url":"https://api.github.com/repos/dart-lang/language/issues/73/comments","statuses_url":"https://api.github.com/repos/dart-lang/language/statuses/e99098bedc158a1d07117d2e81bdd52a632c492c","head":{"label":"dart-lang:terminating-tokens","ref":"terminating-tokens","sha":"e99098bedc158a1d07117d2e81bdd52a632c492c","user":{"login":"dart-lang","id":1609975,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE2MDk5NzU=","avatar_url":"https://avatars0.githubusercontent.com/u/1609975?v=4","gravatar_id":"","url":"https://api.github.com/users/dart-lang","html_url":"https://github.com/dart-lang","followers_url":"https://api.github.com/users/dart-lang/followers","following_url":"https://api.github.com/users/dart-lang/following{/other_user}","gists_url":"https://api.github.com/users/dart-lang/gists{/gist_id}","starred_url":"https://api.github.com/users/dart-lang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dart-lang/subscriptions","organizations_url":"https://api.github.com/users/dart-lang/orgs","repos_url":"https://api.github.com/users/dart-lang/repos","events_url":"https://api.github.com/users/dart-lang/events{/privacy}","received_events_url":"https://api.github.com/users/dart-lang/received_events","type":"Organization","site_admin":false},"repo":{"id":137279731,"node_id":"MDEwOlJlcG9zaXRvcnkxMzcyNzk3MzE=","name":"language","full_name":"dart-lang/language","private":false,"owner":{"login":"dart-lang","id":1609975,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE2MDk5NzU=","avatar_url":"https://avatars0.githubusercontent.com/u/1609975?v=4","gravatar_id":"","url":"https://api.github.com/users/dart-lang","html_url":"https://github.com/dart-lang","followers_url":"https://api.github.com/users/dart-lang/followers","following_url":"https://api.github.com/users/dart-lang/following{/other_user}","gists_url":"https://api.github.com/users/dart-lang/gists{/gist_id}","starred_url":"https://api.github.com/users/dart-lang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dart-lang/subscriptions","organizations_url":"https://api.github.com/users/dart-lang/orgs","repos_url":"https://api.github.com/users/dart-lang/repos","events_url":"https://api.github.com/users/dart-lang/events{/privacy}","received_events_url":"https://api.github.com/users/dart-lang/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/dart-lang/language","description":null,"fork":false,"url":"https://api.github.com/repos/dart-lang/language","forks_url":"https://api.github.com/repos/dart-lang/language/forks","keys_url":"https://api.github.com/repos/dart-lang/language/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dart-lang/language/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dart-lang/language/teams","hooks_url":"https://api.github.com/repos/dart-lang/language/hooks","issue_events_url":"https://api.github.com/repos/dart-lang/language/issues/events{/number}","events_url":"https://api.github.com/repos/dart-lang/language/events","assignees_url":"https://api.github.com/repos/dart-lang/language/assignees{/user}","branches_url":"https://api.github.com/repos/dart-lang/language/branches{/branch}","tags_url":"https://api.github.com/repos/dart-lang/language/tags","blobs_url":"https://api.github.com/repos/dart-lang/language/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dart-lang/language/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dart-lang/language/git/refs{/sha}","trees_url":"https://api.github.com/repos/dart-lang/language/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dart-lang/language/statuses/{sha}","languages_url":"https://api.github.com/repos/dart-lang/language/languages","stargazers_url":"https://api.github.com/repos/dart-lang/language/stargazers","contributors_url":"https://api.github.com/repos/dart-lang/language/contributors","subscribers_url":"https://api.github.com/repos/dart-lang/language/subscribers","subscription_url":"https://api.github.com/repos/dart-lang/language/subscription","commits_url":"https://api.github.com/repos/dart-lang/language/commits{/sha}","git_commits_url":"https://api.github.com/repos/dart-lang/language/git/commits{/sha}","comments_url":"https://api.github.com/repos/dart-lang/language/comments{/number}","issue_comment_url":"https://api.github.com/repos/dart-lang/language/issues/comments{/number}","contents_url":"https://api.github.com/repos/dart-lang/language/contents/{+path}","compare_url":"https://api.github.com/repos/dart-lang/language/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dart-lang/language/merges","archive_url":"https://api.github.com/repos/dart-lang/language/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dart-lang/language/downloads","issues_url":"https://api.github.com/repos/dart-lang/language/issues{/number}","pulls_url":"https://api.github.com/repos/dart-lang/language/pulls{/number}","milestones_url":"https://api.github.com/repos/dart-lang/language/milestones{/number}","notifications_url":"https://api.github.com/repos/dart-lang/language/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dart-lang/language/labels{/name}","releases_url":"https://api.github.com/repos/dart-lang/language/releases{/id}","deployments_url":"https://api.github.com/repos/dart-lang/language/deployments","created_at":"2018-06-13T22:49:18Z","updated_at":"2018-11-14T21:59:58Z","pushed_at":"2018-11-14T20:28:36Z","git_url":"git://github.com/dart-lang/language.git","ssh_url":"git@github.com:dart-lang/language.git","clone_url":"https://github.com/dart-lang/language.git","svn_url":"https://github.com/dart-lang/language","homepage":null,"size":765,"stargazers_count":52,"watchers_count":52,"language":"Dart","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":4,"mirror_url":null,"archived":false,"open_issues_count":66,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":4,"open_issues":66,"watchers":52,"default_branch":"master"}},"base":{"label":"dart-lang:master","ref":"master","sha":"0ae77e4428b093401bef4a82f7aacad07536f388","user":{"login":"dart-lang","id":1609975,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE2MDk5NzU=","avatar_url":"https://avatars0.githubusercontent.com/u/1609975?v=4","gravatar_id":"","url":"https://api.github.com/users/dart-lang","html_url":"https://github.com/dart-lang","followers_url":"https://api.github.com/users/dart-lang/followers","following_url":"https://api.github.com/users/dart-lang/following{/other_user}","gists_url":"https://api.github.com/users/dart-lang/gists{/gist_id}","starred_url":"https://api.github.com/users/dart-lang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dart-lang/subscriptions","organizations_url":"https://api.github.com/users/dart-lang/orgs","repos_url":"https://api.github.com/users/dart-lang/repos","events_url":"https://api.github.com/users/dart-lang/events{/privacy}","received_events_url":"https://api.github.com/users/dart-lang/received_events","type":"Organization","site_admin":false},"repo":{"id":137279731,"node_id":"MDEwOlJlcG9zaXRvcnkxMzcyNzk3MzE=","name":"language","full_name":"dart-lang/language","private":false,"owner":{"login":"dart-lang","id":1609975,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE2MDk5NzU=","avatar_url":"https://avatars0.githubusercontent.com/u/1609975?v=4","gravatar_id":"","url":"https://api.github.com/users/dart-lang","html_url":"https://github.com/dart-lang","followers_url":"https://api.github.com/users/dart-lang/followers","following_url":"https://api.github.com/users/dart-lang/following{/other_user}","gists_url":"https://api.github.com/users/dart-lang/gists{/gist_id}","starred_url":"https://api.github.com/users/dart-lang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dart-lang/subscriptions","organizations_url":"https://api.github.com/users/dart-lang/orgs","repos_url":"https://api.github.com/users/dart-lang/repos","events_url":"https://api.github.com/users/dart-lang/events{/privacy}","received_events_url":"https://api.github.com/users/dart-lang/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/dart-lang/language","description":null,"fork":false,"url":"https://api.github.com/repos/dart-lang/language","forks_url":"https://api.github.com/repos/dart-lang/language/forks","keys_url":"https://api.github.com/repos/dart-lang/language/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dart-lang/language/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dart-lang/language/teams","hooks_url":"https://api.github.com/repos/dart-lang/language/hooks","issue_events_url":"https://api.github.com/repos/dart-lang/language/issues/events{/number}","events_url":"https://api.github.com/repos/dart-lang/language/events","assignees_url":"https://api.github.com/repos/dart-lang/language/assignees{/user}","branches_url":"https://api.github.com/repos/dart-lang/language/branches{/branch}","tags_url":"https://api.github.com/repos/dart-lang/language/tags","blobs_url":"https://api.github.com/repos/dart-lang/language/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dart-lang/language/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dart-lang/language/git/refs{/sha}","trees_url":"https://api.github.com/repos/dart-lang/language/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dart-lang/language/statuses/{sha}","languages_url":"https://api.github.com/repos/dart-lang/language/languages","stargazers_url":"https://api.github.com/repos/dart-lang/language/stargazers","contributors_url":"https://api.github.com/repos/dart-lang/language/contributors","subscribers_url":"https://api.github.com/repos/dart-lang/language/subscribers","subscription_url":"https://api.github.com/repos/dart-lang/language/subscription","commits_url":"https://api.github.com/repos/dart-lang/language/commits{/sha}","git_commits_url":"https://api.github.com/repos/dart-lang/language/git/commits{/sha}","comments_url":"https://api.github.com/repos/dart-lang/language/comments{/number}","issue_comment_url":"https://api.github.com/repos/dart-lang/language/issues/comments{/number}","contents_url":"https://api.github.com/repos/dart-lang/language/contents/{+path}","compare_url":"https://api.github.com/repos/dart-lang/language/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dart-lang/language/merges","archive_url":"https://api.github.com/repos/dart-lang/language/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dart-lang/language/downloads","issues_url":"https://api.github.com/repos/dart-lang/language/issues{/number}","pulls_url":"https://api.github.com/repos/dart-lang/language/pulls{/number}","milestones_url":"https://api.github.com/repos/dart-lang/language/milestones{/number}","notifications_url":"https://api.github.com/repos/dart-lang/language/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dart-lang/language/labels{/name}","releases_url":"https://api.github.com/repos/dart-lang/language/releases{/id}","deployments_url":"https://api.github.com/repos/dart-lang/language/deployments","created_at":"2018-06-13T22:49:18Z","updated_at":"2018-11-14T21:59:58Z","pushed_at":"2018-11-14T20:28:36Z","git_url":"git://github.com/dart-lang/language.git","ssh_url":"git@github.com:dart-lang/language.git","clone_url":"https://github.com/dart-lang/language.git","svn_url":"https://github.com/dart-lang/language","homepage":null,"size":765,"stargazers_count":52,"watchers_count":52,"language":"Dart","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":4,"mirror_url":null,"archived":false,"open_issues_count":66,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":4,"open_issues":66,"watchers":52,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/dart-lang/language/pulls/73"},"html":{"href":"https://github.com/dart-lang/language/pull/73"},"issue":{"href":"https://api.github.com/repos/dart-lang/language/issues/73"},"comments":{"href":"https://api.github.com/repos/dart-lang/language/issues/73/comments"},"review_comments":{"href":"https://api.github.com/repos/dart-lang/language/pulls/73/comments"},"review_comment":{"href":"https://api.github.com/repos/dart-lang/language/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/dart-lang/language/pulls/73/commits"},"statuses":{"href":"https://api.github.com/repos/dart-lang/language/statuses/e99098bedc158a1d07117d2e81bdd52a632c492c"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"munificent"}}