resolver: Remove error codes

We've decided that these will be omitted for now.

Move the check-spec-examples script into the tools/src directory, and update the go modules.
Add a bash script to build and run this.

Change-Id: I852f8ddb1b9b987410a2a49cf6d14e54c3cf3f0e
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/56381
Auto-Submit: Ben Clayton <bclayton@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/resolver/call_validation_test.cc b/src/resolver/call_validation_test.cc
index d2f3196..d94b70d 100644
--- a/src/resolver/call_validation_test.cc
+++ b/src/resolver/call_validation_test.cc
@@ -42,8 +42,7 @@
   EXPECT_FALSE(r()->Resolve());
 
   EXPECT_EQ(r()->error(),
-            "12:34 error v-0004: recursion is not permitted. 'main' attempted "
-            "to call "
+            "12:34 error: recursion is not permitted. 'main' attempted to call "
             "itself.");
 }
 
@@ -70,8 +69,7 @@
 
   EXPECT_FALSE(r()->Resolve());
 
-  EXPECT_EQ(r()->error(),
-            "12:34 error: v-0006: unable to find called function: func");
+  EXPECT_EQ(r()->error(), "12:34 error: unable to find called function: func");
 }
 
 TEST_F(ResolverCallValidationTest, TooFewArgs) {
diff --git a/src/resolver/control_block_validation_test.cc b/src/resolver/control_block_validation_test.cc
index f4db5e1..b53bc0d 100644
--- a/src/resolver/control_block_validation_test.cc
+++ b/src/resolver/control_block_validation_test.cc
@@ -45,8 +45,8 @@
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(r()->error(),
-            "12:34 error v-0025: switch statement selector expression must be "
-            "of a scalar integer type");
+            "12:34 error: switch statement selector expression must be of a "
+            "scalar integer type");
 }
 
 TEST_F(ResolverControlBlockValidationTest, SwitchWithoutDefault_Fail) {
@@ -106,8 +106,7 @@
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(
       r()->error(),
-      "12:34 error v-0008: switch statement must have exactly one default "
-      "clause");
+      "12:34 error: switch statement must have exactly one default clause");
 }
 
 TEST_F(ResolverControlBlockValidationTest, UnreachableCode_continue) {
@@ -164,8 +163,8 @@
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(r()->error(),
-            "12:34 error v-0026: the case selector values must have the same "
-            "type as the selector expression.");
+            "12:34 error: the case selector values must have the same type as "
+            "the selector expression.");
 }
 
 TEST_F(ResolverControlBlockValidationTest,
@@ -193,8 +192,8 @@
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(r()->error(),
-            "12:34 error v-0026: the case selector values must have the same "
-            "type as the selector expression.");
+            "12:34 error: the case selector values must have the same type as "
+            "the selector expression.");
 }
 
 TEST_F(ResolverControlBlockValidationTest,
@@ -228,8 +227,8 @@
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(r()->error(),
-            "12:34 error v-0027: a literal value must not appear more than "
-            "once in the case selectors for a switch statement: '2u'");
+            "12:34 error: a literal value must not appear more than once in "
+            "the case selectors for a switch statement: '2u'");
 }
 
 TEST_F(ResolverControlBlockValidationTest,
@@ -264,10 +263,9 @@
   WrapInFunction(block);
 
   EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error v-0027: a literal value must not appear more than once in "
-      "the case selectors for a switch statement: '10'");
+  EXPECT_EQ(r()->error(),
+            "12:34 error: a literal value must not appear more than once in "
+            "the case selectors for a switch statement: '10'");
 }
 
 TEST_F(ResolverControlBlockValidationTest,
@@ -288,10 +286,9 @@
   WrapInFunction(block);
 
   EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error v-0028: a fallthrough statement must not appear as the "
-      "last statement in last clause of a switch");
+  EXPECT_EQ(r()->error(),
+            "12:34 error: a fallthrough statement must not appear as the last "
+            "statement in last clause of a switch");
 }
 
 TEST_F(ResolverControlBlockValidationTest, SwitchCase_Pass) {
diff --git a/src/resolver/function_validation_test.cc b/src/resolver/function_validation_test.cc
index 6ec8c1f..2ed9ab7 100644
--- a/src/resolver/function_validation_test.cc
+++ b/src/resolver/function_validation_test.cc
@@ -42,7 +42,7 @@
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(r()->error(),
-            R"(12:34 error v-0016: duplicate function named 'func'
+            R"(12:34 error: duplicate function named 'func'
 note: first function declared here)");
 }
 
@@ -74,8 +74,8 @@
 
   EXPECT_FALSE(r()->Resolve()) << r()->error();
   EXPECT_EQ(r()->error(),
-            "12:34 error v-2000: duplicate declaration 'foo'\n56:78 note: "
-            "'foo' first declared here:");
+            "12:34 error: duplicate declaration 'foo'\n56:78 note: 'foo' first "
+            "declared here:");
 }
 
 TEST_F(ResolverFunctionValidationTest,
@@ -91,8 +91,8 @@
 
   EXPECT_FALSE(r()->Resolve()) << r()->error();
   EXPECT_EQ(r()->error(),
-            "error v-2000: duplicate declaration 'foo'\n12:34 note: 'foo' "
-            "first declared here:");
+            "error: duplicate declaration 'foo'\n12:34 note: 'foo' first "
+            "declared here:");
 }
 
 TEST_F(ResolverFunctionValidationTest, FunctionUsingSameVariableName_Pass) {
@@ -163,9 +163,8 @@
        ast::DecorationList{});
 
   EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error v-0002: non-void function must end with a return statement");
+  EXPECT_EQ(r()->error(),
+            "12:34 error: non-void function must end with a return statement");
 }
 
 TEST_F(ResolverFunctionValidationTest,
@@ -186,9 +185,8 @@
        ast::StatementList{}, ast::DecorationList{});
 
   EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error v-0002: non-void function must end with a return statement");
+  EXPECT_EQ(r()->error(),
+            "12:34 error: non-void function must end with a return statement");
 }
 
 TEST_F(ResolverFunctionValidationTest,
@@ -214,8 +212,8 @@
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(r()->error(),
-            "12:34 error v-000y: return statement type must match its function "
-            "return type, returned 'i32', expected 'void'");
+            "12:34 error: return statement type must match its function return "
+            "type, returned 'i32', expected 'void'");
 }
 
 TEST_F(ResolverFunctionValidationTest,
@@ -229,8 +227,8 @@
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(r()->error(),
-            "12:34 error v-000y: return statement type must match its function "
-            "return type, returned 'void', expected 'f32'");
+            "12:34 error: return statement type must match its function return "
+            "type, returned 'void', expected 'f32'");
 }
 
 TEST_F(ResolverFunctionValidationTest,
@@ -256,8 +254,8 @@
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(r()->error(),
-            "12:34 error v-000y: return statement type must match its function "
-            "return type, returned 'i32', expected 'f32'");
+            "12:34 error: return statement type must match its function return "
+            "type, returned 'i32', expected 'f32'");
 }
 
 TEST_F(ResolverFunctionValidationTest,
@@ -287,8 +285,8 @@
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(r()->error(),
-            "12:34 error v-000y: return statement type must match its function "
-            "return type, returned 'u32', expected 'myf32'");
+            "12:34 error: return statement type must match its function return "
+            "type, returned 'u32', expected 'myf32'");
 }
 
 TEST_F(ResolverFunctionValidationTest, PipelineStage_MustBeUnique_Fail) {
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index f613790..ca64b25 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -616,8 +616,7 @@
 
 bool Resolver::GlobalVariable(ast::Variable* var) {
   if (variable_stack_.has(var->symbol())) {
-    AddError("v-0011",
-             "redeclared global identifier '" +
+    AddError("redeclared global identifier '" +
                  builder_->Symbols().NameFor(var->symbol()) + "'",
              var->source());
     return false;
@@ -630,13 +629,11 @@
   variable_stack_.set_global(var->symbol(), info);
 
   if (!var->is_const() && info->storage_class == ast::StorageClass::kNone) {
-    AddError("v-0022", "global variables must have a storage class",
-             var->source());
+    AddError("global variables must have a storage class", var->source());
     return false;
   }
   if (var->is_const() && !(info->storage_class == ast::StorageClass::kNone)) {
-    AddError("v-global01", "global constants shouldn't have a storage class",
-             var->source());
+    AddError("global constants shouldn't have a storage class", var->source());
     return false;
   }
 
@@ -678,8 +675,7 @@
 bool Resolver::ValidateGlobalVariable(const VariableInfo* info) {
   auto duplicate_func = symbol_to_function_.find(info->declaration->symbol());
   if (duplicate_func != symbol_to_function_.end()) {
-    AddError("v-2000",
-             "duplicate declaration '" +
+    AddError("duplicate declaration '" +
                  builder_->Symbols().NameFor(info->declaration->symbol()) + "'",
              info->declaration->source());
     AddNote("'" + builder_->Symbols().NameFor(info->declaration->symbol()) +
@@ -866,8 +862,7 @@
 
   if (auto* r = storage_type->As<sem::Array>()) {
     if (r->IsRuntimeSized()) {
-      AddError("v-0015",
-               "runtime arrays may only appear as the last member of a struct",
+      AddError("runtime arrays may only appear as the last member of a struct",
                var->source());
       return false;
     }
@@ -1040,8 +1035,7 @@
                                 const FunctionInfo* info) {
   auto func_it = symbol_to_function_.find(func->symbol());
   if (func_it != symbol_to_function_.end()) {
-    AddError("v-0016",
-             "duplicate function named '" +
+    AddError("duplicate function named '" +
                  builder_->Symbols().NameFor(func->symbol()) + "'",
              func->source());
     AddNote("first function declared here",
@@ -1053,8 +1047,7 @@
   VariableInfo* var;
   if (variable_stack_.get(func->symbol(), &var, &is_global)) {
     if (is_global) {
-      AddError("v-2000",
-               "duplicate declaration '" +
+      AddError("duplicate declaration '" +
                    builder_->Symbols().NameFor(func->symbol()) + "'",
                func->source());
       AddNote("'" + builder_->Symbols().NameFor(func->symbol()) +
@@ -1099,7 +1092,7 @@
     if (func->body()) {
       if (!func->get_last_statement() ||
           !func->get_last_statement()->Is<ast::ReturnStatement>()) {
-        AddError("v-0002", "non-void function must end with a return statement",
+        AddError("non-void function must end with a return statement",
                  func->source());
         return false;
       }
@@ -2038,13 +2031,11 @@
   if (callee_func_it == symbol_to_function_.end()) {
     if (current_function_ &&
         current_function_->declaration->symbol() == ident->symbol()) {
-      AddError("v-0004",
-               "recursion is not permitted. '" + name +
+      AddError("recursion is not permitted. '" + name +
                    "' attempted to call itself.",
                call->source());
     } else {
-      AddError("v-0006: unable to find called function: " + name,
-               call->source());
+      AddError("unable to find called function: " + name, call->source());
     }
     return false;
   }
@@ -2400,8 +2391,7 @@
     return false;
   }
 
-  AddError("v-0006: identifier must be declared before use: " + name,
-           expr->source());
+  AddError("identifier must be declared before use: " + name, expr->source());
   return false;
 }
 
@@ -2829,9 +2819,7 @@
 
   bool is_global = false;
   if (variable_stack_.get(var->symbol(), nullptr, &is_global)) {
-    const char* error_code = is_global ? "v-0013" : "v-0014";
-    AddError(error_code,
-             "redeclared identifier '" +
+    AddError("redeclared identifier '" +
                  builder_->Symbols().NameFor(var->symbol()) + "'",
              var->source());
     return false;
@@ -3370,18 +3358,16 @@
       if (r->IsRuntimeSized()) {
         if (member != str->Members().back()) {
           AddError(
-              "v-0015",
               "runtime arrays may only appear as the last member of a struct",
               member->Declaration()->source());
           return false;
         }
         if (!str->IsBlockDecorated()) {
-          AddError("v-0015",
-                   "a struct containing a runtime-sized array "
-                   "requires the [[block]] attribute: '" +
-                       builder_->Symbols().NameFor(str->Declaration()->name()) +
-                       "'",
-                   member->Declaration()->source());
+          AddError(
+              "a struct containing a runtime-sized array "
+              "requires the [[block]] attribute: '" +
+                  builder_->Symbols().NameFor(str->Declaration()->name()) + "'",
+              member->Declaration()->source());
           return false;
         }
       }
@@ -3568,12 +3554,12 @@
                                     : builder_->create<sem::Void>();
 
   if (func_type->UnwrapRef() != ret_type) {
-    AddError("v-000y",
-             "return statement type must match its function "
-             "return type, returned '" +
-                 ret_type->FriendlyName(builder_->Symbols()) + "', expected '" +
-                 current_function_->return_type_name + "'",
-             ret->source());
+    AddError(
+        "return statement type must match its function "
+        "return type, returned '" +
+            ret_type->FriendlyName(builder_->Symbols()) + "', expected '" +
+            current_function_->return_type_name + "'",
+        ret->source());
     return false;
   }
 
@@ -3598,10 +3584,10 @@
 bool Resolver::ValidateSwitch(const ast::SwitchStatement* s) {
   auto* cond_type = TypeOf(s->condition())->UnwrapRef();
   if (!cond_type->is_integer_scalar()) {
-    AddError("v-0025",
-             "switch statement selector expression must be of a "
-             "scalar integer type",
-             s->condition()->source());
+    AddError(
+        "switch statement selector expression must be of a "
+        "scalar integer type",
+        s->condition()->source());
     return false;
   }
 
@@ -3612,8 +3598,7 @@
     if (case_stmt->IsDefault()) {
       if (has_default) {
         // More than one default clause
-        AddError("v-0008",
-                 "switch statement must have exactly one default clause",
+        AddError("switch statement must have exactly one default clause",
                  case_stmt->source());
         return false;
       }
@@ -3622,20 +3607,20 @@
 
     for (auto* selector : case_stmt->selectors()) {
       if (cond_type != TypeOf(selector)) {
-        AddError("v-0026",
-                 "the case selector values must have the same "
-                 "type as the selector expression.",
-                 case_stmt->source());
+        AddError(
+            "the case selector values must have the same "
+            "type as the selector expression.",
+            case_stmt->source());
         return false;
       }
 
       auto v = selector->value_as_u32();
       if (selector_set.find(v) != selector_set.end()) {
-        AddError("v-0027",
-                 "a literal value must not appear more than once in "
-                 "the case selectors for a switch statement: '" +
-                     builder_->str(selector) + "'",
-                 case_stmt->source());
+        AddError(
+            "a literal value must not appear more than once in "
+            "the case selectors for a switch statement: '" +
+                builder_->str(selector) + "'",
+            case_stmt->source());
         return false;
       }
       selector_set.emplace(v);
@@ -3652,10 +3637,10 @@
     auto* last_clause = s->body().back()->As<ast::CaseStatement>();
     auto* last_stmt = last_clause->body()->last();
     if (last_stmt && last_stmt->Is<ast::FallthroughStatement>()) {
-      AddError("v-0028",
-               "a fallthrough statement must not appear as "
-               "the last statement in last clause of a switch",
-               last_stmt->source());
+      AddError(
+          "a fallthrough statement must not appear as "
+          "the last statement in last clause of a switch",
+          last_stmt->source());
       return false;
     }
   }
@@ -3875,12 +3860,6 @@
       << "Pointer: " << node;
 }
 
-void Resolver::AddError(const char* code,
-                        const std::string& msg,
-                        const Source& source) const {
-  diagnostics_.add_error(diag::System::Resolver, code, msg, source);
-}
-
 void Resolver::AddError(const std::string& msg, const Source& source) const {
   diagnostics_.add_error(diag::System::Resolver, msg, source);
 }
diff --git a/src/resolver/resolver.h b/src/resolver/resolver.h
index 16758bf..0506a81 100644
--- a/src/resolver/resolver.h
+++ b/src/resolver/resolver.h
@@ -412,12 +412,6 @@
   void Mark(const ast::Node* node);
 
   /// Adds the given error message to the diagnostics
-  /// [DEPRECATED] Remove all codes
-  void AddError(const char* code,
-                const std::string& msg,
-                const Source& source) const;
-
-  /// Adds the given error message to the diagnostics
   void AddError(const std::string& msg, const Source& source) const;
 
   /// Adds the given warning message to the diagnostics
diff --git a/src/resolver/storage_class_validation_test.cc b/src/resolver/storage_class_validation_test.cc
index 5c6ca3d..96c4666 100644
--- a/src/resolver/storage_class_validation_test.cc
+++ b/src/resolver/storage_class_validation_test.cc
@@ -31,7 +31,7 @@
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(r()->error(),
-            "12:34 error v-0022: global variables must have a storage class");
+            "12:34 error: global variables must have a storage class");
 }
 
 TEST_F(ResolverStorageClassValidationTest, StorageBufferBool) {
diff --git a/src/resolver/type_validation_test.cc b/src/resolver/type_validation_test.cc
index b1aea9c..fd73e05 100644
--- a/src/resolver/type_validation_test.cc
+++ b/src/resolver/type_validation_test.cc
@@ -100,8 +100,7 @@
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(r()->error(),
-            "12:34 error v-global01: global constants shouldn't have a storage "
-            "class");
+            "12:34 error: global constants shouldn't have a storage class");
 }
 
 TEST_F(ResolverTypeValidationTest, GlobalConstNoStorageClass_Pass) {
@@ -216,10 +215,9 @@
        });
 
   EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error v-0015: runtime arrays may only appear as the last member "
-      "of a struct");
+  EXPECT_EQ(r()->error(),
+            "12:34 error: runtime arrays may only appear as the last member of "
+            "a struct");
 }
 
 TEST_F(ResolverTypeValidationTest, RuntimeArrayIsLast_Pass) {
@@ -256,8 +254,8 @@
 
   EXPECT_FALSE(r()->Resolve()) << r()->error();
   EXPECT_EQ(r()->error(),
-            "12:34 error v-0015: a struct containing a runtime-sized array "
-            "requires the [[block]] attribute: 'Foo'");
+            "12:34 error: a struct containing a runtime-sized array requires "
+            "the [[block]] attribute: 'Foo'");
 }
 
 TEST_F(ResolverTypeValidationTest, RuntimeArrayIsNotLast_Fail) {
@@ -279,7 +277,7 @@
   EXPECT_FALSE(r()->Resolve()) << r()->error();
   EXPECT_EQ(
       r()->error(),
-      R"(12:34 error v-0015: runtime arrays may only appear as the last member of a struct)");
+      R"(12:34 error: runtime arrays may only appear as the last member of a struct)");
 }
 
 TEST_F(ResolverTypeValidationTest, RuntimeArrayAsGlobalVariable) {
@@ -289,7 +287,7 @@
 
   EXPECT_EQ(
       r()->error(),
-      R"(56:78 error v-0015: runtime arrays may only appear as the last member of a struct)");
+      R"(56:78 error: runtime arrays may only appear as the last member of a struct)");
 }
 
 TEST_F(ResolverTypeValidationTest, RuntimeArrayAsLocalVariable) {
@@ -300,7 +298,7 @@
 
   EXPECT_EQ(
       r()->error(),
-      R"(56:78 error v-0015: runtime arrays may only appear as the last member of a struct)");
+      R"(56:78 error: runtime arrays may only appear as the last member of a struct)");
 }
 
 TEST_F(ResolverTypeValidationTest, RuntimeArrayAsParameter_Fail) {
@@ -324,10 +322,9 @@
        });
 
   EXPECT_FALSE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error v-0015: runtime arrays may only appear as the last member "
-      "of a struct");
+  EXPECT_EQ(r()->error(),
+            "12:34 error: runtime arrays may only appear as the last member of "
+            "a struct");
 }
 
 TEST_F(ResolverTypeValidationTest, AliasRuntimeArrayIsNotLast_Fail) {
@@ -349,10 +346,9 @@
   WrapInFunction();
 
   EXPECT_FALSE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(
-      r()->error(),
-      "12:34 error v-0015: runtime arrays may only appear as the last member "
-      "of a struct");
+  EXPECT_EQ(r()->error(),
+            "12:34 error: runtime arrays may only appear as the last member of "
+            "a struct");
 }
 
 TEST_F(ResolverTypeValidationTest, AliasRuntimeArrayIsLast_Pass) {
diff --git a/src/resolver/validation_test.cc b/src/resolver/validation_test.cc
index 3bd881e..33d4693 100644
--- a/src/resolver/validation_test.cc
+++ b/src/resolver/validation_test.cc
@@ -213,7 +213,7 @@
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(r()->error(),
-            "12:34 error: v-0006: identifier must be declared before use: b");
+            "12:34 error: identifier must be declared before use: b");
 }
 
 TEST_F(ResolverValidationTest, UsingUndefinedVariableInBlockStatement_Fail) {
@@ -229,7 +229,7 @@
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(r()->error(),
-            "12:34 error: v-0006: identifier must be declared before use: b");
+            "12:34 error: identifier must be declared before use: b");
 }
 
 TEST_F(ResolverValidationTest, UsingUndefinedVariableGlobalVariableAfter_Fail) {
@@ -251,8 +251,7 @@
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(r()->error(),
-            "12:34 error: v-0006: identifier must be declared before use: "
-            "global_var");
+            "12:34 error: identifier must be declared before use: global_var");
 }
 
 TEST_F(ResolverValidationTest, UsingUndefinedVariableGlobalVariable_Pass) {
@@ -295,7 +294,7 @@
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(r()->error(),
-            "12:34 error: v-0006: identifier must be declared before use: a");
+            "12:34 error: identifier must be declared before use: a");
 }
 
 TEST_F(ResolverValidationTest, UsingUndefinedVariableOuterScope_Pass) {
@@ -338,7 +337,7 @@
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(r()->error(),
-            "12:34 error: v-0006: identifier must be declared before use: a");
+            "12:34 error: identifier must be declared before use: a");
 }
 
 TEST_F(ResolverValidationTest, StorageClass_FunctionVariableWorkgroupClass) {
diff --git a/src/resolver/var_let_validation_test.cc b/src/resolver/var_let_validation_test.cc
index f7fa006..e03e6e7 100644
--- a/src/resolver/var_let_validation_test.cc
+++ b/src/resolver/var_let_validation_test.cc
@@ -124,7 +124,7 @@
   WrapInFunction(v1, v2);
 
   EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error v-0014: redeclared identifier 'v'");
+  EXPECT_EQ(r()->error(), "12:34 error: redeclared identifier 'v'");
 }
 
 TEST_F(ResolverVarLetValidationTest, LocalLetRedeclared) {
@@ -135,7 +135,7 @@
   WrapInFunction(l1, l2);
 
   EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error v-0014: redeclared identifier 'l'");
+  EXPECT_EQ(r()->error(), "12:34 error: redeclared identifier 'l'");
 }
 
 TEST_F(ResolverVarLetValidationTest, GlobalVarRedeclared) {
@@ -146,7 +146,7 @@
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(r()->error(),
-            "12:34 error v-0011: redeclared global identifier 'v'");
+            "12:34 error: redeclared global identifier 'v'");
 }
 
 TEST_F(ResolverVarLetValidationTest, GlobalLetRedeclared) {
@@ -157,7 +157,7 @@
 
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(r()->error(),
-            "12:34 error v-0011: redeclared global identifier 'l'");
+            "12:34 error: redeclared global identifier 'l'");
 }
 
 TEST_F(ResolverVarLetValidationTest, GlobalVarRedeclaredAsLocal) {
@@ -173,7 +173,7 @@
                      Expr(2.0f)));
 
   EXPECT_FALSE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(r()->error(), "12:34 error v-0013: redeclared identifier 'v'");
+  EXPECT_EQ(r()->error(), "12:34 error: redeclared identifier 'v'");
 }
 
 TEST_F(ResolverVarLetValidationTest, VarRedeclaredInInnerBlock) {
@@ -190,7 +190,7 @@
   WrapInFunction(outer_body);
 
   EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error v-0014: redeclared identifier 'v'");
+  EXPECT_EQ(r()->error(), "12:34 error: redeclared identifier 'v'");
 }
 
 TEST_F(ResolverVarLetValidationTest, VarRedeclaredInIfBlock) {
@@ -213,7 +213,7 @@
   WrapInFunction(outer_body);
 
   EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(), "12:34 error v-0014: redeclared identifier 'v'");
+  EXPECT_EQ(r()->error(), "12:34 error: redeclared identifier 'v'");
 }
 
 TEST_F(ResolverVarLetValidationTest, InferredPtrStorageAccessMismatch) {
diff --git a/tools/check-spec-examples b/tools/check-spec-examples
new file mode 100755
index 0000000..fa10151
--- /dev/null
+++ b/tools/check-spec-examples
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+# Copyright 2021 The Tint Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -e # Fail on any error.
+
+if [ ! -x "$(which go)" ] ; then
+    echo "error: go needs to be on \$PATH to use $0"
+    exit 1
+fi
+
+SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )"
+ROOT_DIR="$( cd "${SCRIPT_DIR}/.." >/dev/null 2>&1 && pwd )"
+BINARY="${SCRIPT_DIR}/bin/check-spec-examples"
+
+# Rebuild the binary.
+# Note, go caches build artifacts, so this is quick for repeat calls
+pushd "${SCRIPT_DIR}/src/cmd/check-spec-examples" > /dev/null
+    go build -o "${BINARY}" main.go
+popd > /dev/null
+
+"${BINARY}" "$@"
diff --git a/tools/check-spec-examples/main.go b/tools/src/cmd/check-spec-examples/main.go
similarity index 94%
rename from tools/check-spec-examples/main.go
rename to tools/src/cmd/check-spec-examples/main.go
index 944b28b..733b340 100644
--- a/tools/check-spec-examples/main.go
+++ b/tools/src/cmd/check-spec-examples/main.go
@@ -42,7 +42,7 @@
 
 const (
 	toolName        = "check-spec-examples"
-	defaultSpecPath = "https://gpuweb.github.io/gpuweb/wgsl.html"
+	defaultSpecPath = "https://gpuweb.github.io/gpuweb/wgsl/"
 )
 
 var (
@@ -152,6 +152,10 @@
 		return err
 	}
 
+	if len(examples) == 0 {
+		return fmt.Errorf("no examples found")
+	}
+
 	// Create a temporary directory to hold the examples as separate files
 	tmpDir, err := ioutil.TempDir("", "wgsl-spec-examples")
 	if err != nil {
@@ -193,9 +197,8 @@
 // tryCompile attempts to compile the example e in the directory wd, using the
 // compiler at the given path. If the example is annotated with 'function-scope'
 // then the code is wrapped with a basic vertex-stage-entry function.
-// If the first compile fails with an error message containing 'error v-0003',
-// then a dummy vertex-state-entry function is appended to the source, and
-// another attempt to compile the shader is made.
+// If the first compile fails then a dummy vertex-state-entry function is
+// appended to the source, and another attempt to compile the shader is made.
 func tryCompile(compiler, wd string, e example) error {
 	code := e.code
 	if e.functionScope {
@@ -209,9 +212,7 @@
 			return nil
 		}
 
-		if !addedStubFunction && strings.Contains(err.Error(), "error v-0003") {
-			// error v-0003: At least one of vertex, fragment or compute shader
-			// must be present. Add a stub entry point to satisfy the compiler.
+		if !addedStubFunction {
 			code += "\n[[stage(vertex)]] fn main() {}\n"
 			addedStubFunction = true
 			continue
diff --git a/tools/src/go.mod b/tools/src/go.mod
index cf82c77..1f580d5 100644
--- a/tools/src/go.mod
+++ b/tools/src/go.mod
@@ -5,4 +5,5 @@
 require (
 	github.com/fatih/color v1.10.0
 	github.com/sergi/go-diff v1.2.0
+	golang.org/x/net v0.0.0-20210614182718-04defd469f4e
 )
diff --git a/tools/src/go.sum b/tools/src/go.sum
index 5d71093..b2db088 100644
--- a/tools/src/go.sum
+++ b/tools/src/go.sum
@@ -17,9 +17,16 @@
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
+golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
 golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=