diff --git a/docs/core-module/2-pwst.html b/docs/core-module/2-pwst.html index 65534a632..70711da2c 100644 --- a/docs/core-module/2-pwst.html +++ b/docs/core-module/2-pwst.html @@ -117,13 +117,6 @@ group. "which I don't recognise (which is not fine). Specifically, %2."); Problems::issue_problem_end(); break; - case PROJECT_MALFORMED_CE: - Problems::quote_stream(1, CE->details); - StandardProblems::handmade_problem(Task::syntax_tree(), _p_(Untestable)); - Problems::issue_problem_segment( - "This project seems to be malformed. Specifically, %1."); - Problems::issue_problem_end(); - break; case METADATA_MALFORMED_CE: case MALFORMED_LICENCE_CE: if (CE->copy->found_by) { @@ -647,6 +640,18 @@ group. default: internal_error("an unknown error occurred"); } } + LOOP_OVER_LINKED_LIST(CE, copy_error, C->warnings) { + switch (CE->error_category) { + case PROJECT_EXTRANEOUS_CE: + Problems::quote_stream(1, CE->details); + StandardProblems::handmade_warning(Task::syntax_tree(), _p_(Untestable)); + Problems::issue_problem_segment( + "This project contains some unexpected files or directories. Specifically, %1."); + Problems::issue_warning_end(); + break; + } + } + C->warnings = NULL; } void SourceProblems::quote_genre(int N, copy_error *CE) { diff --git a/docs/core-module/2-up.html b/docs/core-module/2-up.html index f4478cfdd..3c4dfae65 100644 --- a/docs/core-module/2-up.html +++ b/docs/core-module/2-up.html @@ -102,7 +102,7 @@ non-problem messages when everything was fine. That all happens here: void UsingProblems::final_report(int disaster_struck, int problems_count) { int total_words = 0; - if (problem_count > 0) { + if (problems_count > 0) { if (problems_file_active) ProblemBuffer::redirect_problem_stream(problems_file); Problems::issue_problem_begin(Task::syntax_tree(), "*"); if (disaster_struck) Issue problem summary for an internal error3.1 diff --git a/docs/problems-module/2-pl2.html b/docs/problems-module/2-pl2.html index c31d8661e..a554550d5 100644 --- a/docs/problems-module/2-pl2.html +++ b/docs/problems-module/2-pl2.html @@ -140,11 +140,12 @@ the two possibilities.
 int do_not_locate_problems = FALSE;
+int warning_count = 0;
 
 void Problems::show_problem_location(parse_node_tree *T) {
     parse_node *problem_headings[NO_HEADING_LEVELS];
     int i, f = FALSE;
-    if (problem_count == 0) {
+    if (problem_count + warning_count == 0) {
         #ifdef FIRST_PROBLEMS_CALLBACK
         FIRST_PROBLEMS_CALLBACK(problems_file);
         #endif
@@ -392,8 +393,6 @@ but we're finally giving way on that.
 

-int warning_count = 0;
-
 int Problems::warnings_occurred(void) {
     return (warning_count > 0)?TRUE:FALSE;
 }
diff --git a/docs/supervisor-module/2-ce.html b/docs/supervisor-module/2-ce.html
index 6daa4e3f9..34d52b924 100644
--- a/docs/supervisor-module/2-ce.html
+++ b/docs/supervisor-module/2-ce.html
@@ -79,7 +79,7 @@ fields are blank.
 enum EXT_TITLE_TOO_LONG_CE
 enum EXT_AUTHOR_TOO_LONG_CE
 enum EXT_RANEOUS_CE
-enum PROJECT_MALFORMED_CE
+enum PROJECT_EXTRANEOUS_CE
 enum LANGUAGE_UNAVAILABLE_CE
 enum LANGUAGE_DEFICIENT_CE
 enum LEXER_CE  an error generated by the words module
@@ -200,7 +200,7 @@ output.
         case EXT_BAD_DIRNAME_CE: WRITE("extension directory name wrong: %S", CE->details); break;
         case EXT_BAD_FILENAME_CE: WRITE("extension filename wrong: %S", CE->details); break;
         case EXT_RANEOUS_CE: WRITE("extraneous content: %S", CE->details); break;
-        case PROJECT_MALFORMED_CE: WRITE("project malformed: %S", CE->details); break;
+        case PROJECT_EXTRANEOUS_CE: WRITE("project has extraneous content: %S", CE->details); break;
         case METADATA_MALFORMED_CE: WRITE("%S has incorrect metadata: %S",
             CE->copy->edition->work->genre->genre_name, CE->details); break;
         case MALFORMED_LICENCE_CE: WRITE("%S has incorrect 'legal-metadata': %S",
diff --git a/docs/supervisor-module/2-cps.html b/docs/supervisor-module/2-cps.html
index 9f0ab38d5..7c2f6fc20 100644
--- a/docs/supervisor-module/2-cps.html
+++ b/docs/supervisor-module/2-cps.html
@@ -86,6 +86,7 @@ stored here.
     struct wording source_text;  the source text we read, if so
     struct inbuild_requirement *found_by;  if this was claimed in a search
     struct linked_list *errors_reading_source_text;  of copy_error
+    struct linked_list *warnings;  of copy_error
     int last_scanned;
     struct inbuild_licence *licence;  optional licence declaration which seems to apply
     CLASS_DEFINITION
@@ -111,6 +112,7 @@ claiming. If you are a manager, do not call this...
     copy->source_text = EMPTY_WORDING;
     copy->found_by = NULL;
     copy->errors_reading_source_text = NEW_LINKED_LIST(copy_error);
+    copy->warnings = NEW_LINKED_LIST(copy_error);
     copy->last_scanned = 0;
     copy->licence = Licences::new(copy);
     return copy;
@@ -157,13 +159,19 @@ for later reporting. These are stored in a list.
 

-void Copies::attach_error(inbuild_copy *C, copy_error *CE) {
+void Copies::attach_error(inbuild_copy *C, copy_error *CE) {
     if (C == NULL) internal_error("no copy to attach to");
     CopyErrors::supply_attached_copy(CE, C);
     ADD_TO_LINKED_LIST(CE, copy_error, C->errors_reading_source_text);
 }
 
-void Copies::list_attached_errors(OUTPUT_STREAM, inbuild_copy *C) {
+void Copies::attach_warning(inbuild_copy *C, copy_error *CE) {
+    if (C == NULL) internal_error("no copy to attach to");
+    CopyErrors::supply_attached_copy(CE, C);
+    ADD_TO_LINKED_LIST(CE, copy_error, C->warnings);
+}
+
+void Copies::list_attached_errors(OUTPUT_STREAM, inbuild_copy *C) {
     if (C == NULL) return;
     copy_error *CE;
     int c = 1;
@@ -172,7 +180,16 @@ for later reporting. These are stored in a list.
     }
 }
 
-void Copies::list_attached_errors_to_HTML(OUTPUT_STREAM, inbuild_copy *C) {
+void Copies::list_attached_warnings(OUTPUT_STREAM, inbuild_copy *C) {
+    if (C == NULL) return;
+    copy_error *CE;
+    int c = 1;
+    LOOP_OVER_LINKED_LIST(CE, copy_error, C->warnings) {
+        WRITE("%d. ", c++); CopyErrors::write(OUT, CE); WRITE("\n");
+    }
+}
+
+void Copies::list_attached_errors_to_HTML(OUTPUT_STREAM, inbuild_copy *C) {
     if (C == NULL) return;
     HTML_OPEN("ul"); WRITE("\n");
     copy_error *CE;
@@ -184,7 +201,7 @@ for later reporting. These are stored in a list.
     HTML_CLOSE("ul"); WRITE("\n");
 }
 
-void Copies::list_attached_errors_to_JSON(JSON_value *errors, inbuild_copy *C) {
+void Copies::list_attached_errors_to_JSON(JSON_value *errors, inbuild_copy *C) {
     if (C == NULL) return;
     copy_error *CE;
     LOOP_OVER_LINKED_LIST(CE, copy_error, C->errors_reading_source_text) {
@@ -199,19 +216,19 @@ for later reporting. These are stored in a list.
 

§6. Writing.

-void Copies::write_copy(OUTPUT_STREAM, inbuild_copy *C) {
+void Copies::write_copy(OUTPUT_STREAM, inbuild_copy *C) {
     Editions::write(OUT, C->edition);
 }
 

§7. Reading source text.

-int Copies::source_text_has_been_read(inbuild_copy *C) {
+int Copies::source_text_has_been_read(inbuild_copy *C) {
     if (C == NULL) internal_error("no copy");
     return C->source_text_read;
 }
 
-wording Copies::get_source_text(inbuild_copy *C, text_stream *reason) {
+wording Copies::get_source_text(inbuild_copy *C, text_stream *reason) {
     if (C->source_text_read == FALSE) {
         C->source_text_read = TRUE;
         if (LinkedLists::len(C->errors_reading_source_text) > 0) {
@@ -229,7 +246,7 @@ for later reporting. These are stored in a list.
 

§8. Going operational.

-void Copies::construct_graph(inbuild_copy *C) {
+void Copies::construct_graph(inbuild_copy *C) {
     if (C->graph_constructed == FALSE) {
         C->graph_constructed = TRUE;
         VOID_METHOD_CALL(C->edition->work->genre, GENRE_CONSTRUCT_GRAPH_MTID, C);
@@ -247,13 +264,13 @@ such newcomers are graphed too.
 

-build_vertex *Copies::construct_project_graph(inbuild_copy *C) {
+build_vertex *Copies::construct_project_graph(inbuild_copy *C) {
     build_vertex *V = Copies::building_soon(C);
     Copies::graph_everything();
     return V;
 }
 
-void Copies::graph_everything(void) {
+void Copies::graph_everything(void) {
     inbuild_copy *C;
     LOOP_OVER(C, inbuild_copy) Copies::construct_graph(C);
 }
@@ -345,6 +362,15 @@ such newcomers are graphed too.
         if (N > 0) {
             INDENT; Copies::list_attached_errors(OUT, C); OUTDENT;
         }
+        N = LinkedLists::len(C->warnings);
+        if (N > 0) {
+            WRITE(" - %d warning", N);
+            if (N > 1) WRITE("s");
+        }
+        WRITE("\n");
+        if (N > 0) {
+            INDENT; Copies::list_attached_errors(OUT, C); OUTDENT;
+        }
     }
 }
 
@@ -397,7 +423,7 @@ its main task: building an Inform project.

-int Copies::copy_to(inbuild_copy *C, inbuild_nest *destination_nest, int syncing,
+int Copies::copy_to(inbuild_copy *C, inbuild_nest *destination_nest, int syncing,
     build_methodology *meth) {
     int rv = 0;
     if (destination_nest) {
@@ -407,7 +433,7 @@ its main task: building an Inform project.
     return rv;
 }
 
-void Copies::overwrite_error(inbuild_copy *C, inbuild_nest *N) {
+void Copies::overwrite_error(inbuild_copy *C, inbuild_nest *N) {
     text_stream *ext = Str::new();
     WRITE_TO(ext, "%X", C->edition->work);
     Errors::with_text("already present (to overwrite, use -sync-to not -copy-to): '%S'", ext);
diff --git a/docs/supervisor-module/4-pbm.html b/docs/supervisor-module/4-pbm.html
index 077c49a64..298a88de6 100644
--- a/docs/supervisor-module/4-pbm.html
+++ b/docs/supervisor-module/4-pbm.html
@@ -163,7 +163,7 @@ not a file, false if we know the reverse, and otherwise not applicable.
                     "the project directory '%S' contains a subdirectory called '%S', "
                     "which I don't recognise",
                     Pathnames::directory_name(P), subdir);
-                Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text));
+                Copies::attach_warning(C, CopyErrors::new_T(PROJECT_EXTRANEOUS_CE, -1, error_text));
                 DISCARD_TEXT(error_text)
             }
             DISCARD_TEXT(subdir)
@@ -182,7 +182,7 @@ not a file, false if we know the reverse, and otherwise not applicable.
                 "the project directory '%S' contains a file called '%S', "
                 "which I don't recognise",
                 Pathnames::directory_name(P), entry);
-            Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text));
+            Copies::attach_warning(C, CopyErrors::new_T(PROJECT_EXTRANEOUS_CE, -1, error_text));
             DISCARD_TEXT(error_text)
         }
     }
@@ -222,7 +222,7 @@ not a file, false if we know the reverse, and otherwise not applicable.
                 "the 'Source' subdirectory of the project directory '%S' contains a "
                 "further subdirectory called '%S', but should not have further subdirectories",
                 Pathnames::directory_name(P), subdir);
-            Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text));
+            Copies::attach_warning(C, CopyErrors::new_T(PROJECT_EXTRANEOUS_CE, -1, error_text));
             DISCARD_TEXT(error_text)
             DISCARD_TEXT(subdir)
         } else {
@@ -232,7 +232,7 @@ not a file, false if we know the reverse, and otherwise not applicable.
                 "the 'Source' subdirectory of the project directory '%S' contains a "
                 "file called '%S', but should only contain the source text file 'story.ni'",
                 Pathnames::directory_name(P), entry);
-            Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text));
+            Copies::attach_warning(C, CopyErrors::new_T(PROJECT_EXTRANEOUS_CE, -1, error_text));
             DISCARD_TEXT(error_text)
         }
     }
@@ -256,7 +256,7 @@ not a file, false if we know the reverse, and otherwise not applicable.
                     "the 'Build' subdirectory of the project directory '%S' contains a "
                     "further subdirectory called '%S', but should not have further subdirectories",
                     Pathnames::directory_name(P), subdir);
-                Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text));
+                Copies::attach_warning(C, CopyErrors::new_T(PROJECT_EXTRANEOUS_CE, -1, error_text));
                 DISCARD_TEXT(error_text)
             }
             DISCARD_TEXT(subdir)
@@ -285,7 +285,7 @@ not a file, false if we know the reverse, and otherwise not applicable.
                     "the 'Index' subdirectory of the project directory '%S' contains a "
                     "further subdirectory called '%S', but can only have one, 'Details'",
                     Pathnames::directory_name(P), subdir);
-                Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text));
+                Copies::attach_warning(C, CopyErrors::new_T(PROJECT_EXTRANEOUS_CE, -1, error_text));
                 DISCARD_TEXT(error_text)
             }
             DISCARD_TEXT(subdir)
@@ -311,7 +311,7 @@ not a file, false if we know the reverse, and otherwise not applicable.
                     "the 'Index' subdirectory of the project directory '%S' contains a "
                     "file called '%S', but can only contain HTML and XML files",
                     Pathnames::directory_name(P), entry);
-                Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text));
+                Copies::attach_warning(C, CopyErrors::new_T(PROJECT_EXTRANEOUS_CE, -1, error_text));
                 DISCARD_TEXT(error_text)
             }
         }
diff --git a/inbuild/supervisor-module/Chapter 2/Copies.w b/inbuild/supervisor-module/Chapter 2/Copies.w
index bda66e7e0..ea9622f1f 100644
--- a/inbuild/supervisor-module/Chapter 2/Copies.w	
+++ b/inbuild/supervisor-module/Chapter 2/Copies.w	
@@ -27,6 +27,7 @@ typedef struct inbuild_copy {
 	struct wording source_text; /* the source text we read, if so */
 	struct inbuild_requirement *found_by; /* if this was claimed in a search */
 	struct linked_list *errors_reading_source_text; /* of |copy_error| */
+	struct linked_list *warnings; /* of |copy_error| */
 	int last_scanned;
 	struct inbuild_licence *licence; /* optional licence declaration which seems to apply */
 	CLASS_DEFINITION
@@ -50,6 +51,7 @@ inbuild_copy *Copies::new_p(inbuild_edition *edition) {
 	copy->source_text = EMPTY_WORDING;
 	copy->found_by = NULL;
 	copy->errors_reading_source_text = NEW_LINKED_LIST(copy_error);
+	copy->warnings = NEW_LINKED_LIST(copy_error);
 	copy->last_scanned = 0;
 	copy->licence = Licences::new(copy);
 	return copy;
@@ -100,6 +102,12 @@ void Copies::attach_error(inbuild_copy *C, copy_error *CE) {
 	ADD_TO_LINKED_LIST(CE, copy_error, C->errors_reading_source_text);
 }
 
+void Copies::attach_warning(inbuild_copy *C, copy_error *CE) {
+	if (C == NULL) internal_error("no copy to attach to");
+	CopyErrors::supply_attached_copy(CE, C);
+	ADD_TO_LINKED_LIST(CE, copy_error, C->warnings);
+}
+
 void Copies::list_attached_errors(OUTPUT_STREAM, inbuild_copy *C) {
 	if (C == NULL) return;
 	copy_error *CE;
@@ -109,6 +117,15 @@ void Copies::list_attached_errors(OUTPUT_STREAM, inbuild_copy *C) {
 	}
 }
 
+void Copies::list_attached_warnings(OUTPUT_STREAM, inbuild_copy *C) {
+	if (C == NULL) return;
+	copy_error *CE;
+	int c = 1;
+	LOOP_OVER_LINKED_LIST(CE, copy_error, C->warnings) {
+		WRITE("%d. ", c++); CopyErrors::write(OUT, CE); WRITE("\n");
+	}
+}
+
 void Copies::list_attached_errors_to_HTML(OUTPUT_STREAM, inbuild_copy *C) {
 	if (C == NULL) return;
 	HTML_OPEN("ul"); WRITE("\n");
@@ -280,6 +297,15 @@ void Copies::inspect(OUTPUT_STREAM, JSON_value *obj, inbuild_copy *C) {
 		if (N > 0) {
 			INDENT; Copies::list_attached_errors(OUT, C); OUTDENT;
 		}
+		N = LinkedLists::len(C->warnings);
+		if (N > 0) {
+			WRITE(" - %d warning", N);
+			if (N > 1) WRITE("s");
+		}
+		WRITE("\n");
+		if (N > 0) {
+			INDENT; Copies::list_attached_errors(OUT, C); OUTDENT;
+		}
 	}
 }
 
diff --git a/inbuild/supervisor-module/Chapter 2/Copy Errors.w b/inbuild/supervisor-module/Chapter 2/Copy Errors.w
index 6cc4f090d..a65b07a4b 100644
--- a/inbuild/supervisor-module/Chapter 2/Copy Errors.w	
+++ b/inbuild/supervisor-module/Chapter 2/Copy Errors.w	
@@ -21,7 +21,7 @@ fields are blank.
 @e EXT_TITLE_TOO_LONG_CE
 @e EXT_AUTHOR_TOO_LONG_CE
 @e EXT_RANEOUS_CE
-@e PROJECT_MALFORMED_CE
+@e PROJECT_EXTRANEOUS_CE
 @e LANGUAGE_UNAVAILABLE_CE
 @e LANGUAGE_DEFICIENT_CE
 @e LEXER_CE /* an error generated by the |words| module */
@@ -137,7 +137,7 @@ void CopyErrors::write(OUTPUT_STREAM, copy_error *CE) {
 		case EXT_BAD_DIRNAME_CE: WRITE("extension directory name wrong: %S", CE->details); break;
 		case EXT_BAD_FILENAME_CE: WRITE("extension filename wrong: %S", CE->details); break;
 		case EXT_RANEOUS_CE: WRITE("extraneous content: %S", CE->details); break;
-		case PROJECT_MALFORMED_CE: WRITE("project malformed: %S", CE->details); break;
+		case PROJECT_EXTRANEOUS_CE: WRITE("project has extraneous content: %S", CE->details); break;
 		case METADATA_MALFORMED_CE: WRITE("%S has incorrect metadata: %S",
 			CE->copy->edition->work->genre->genre_name, CE->details); break;
 		case MALFORMED_LICENCE_CE: WRITE("%S has incorrect 'legal-metadata': %S",
diff --git a/inbuild/supervisor-module/Chapter 4/Project Bundle Manager.w b/inbuild/supervisor-module/Chapter 4/Project Bundle Manager.w
index b1dc0c3c4..94b487f88 100644
--- a/inbuild/supervisor-module/Chapter 4/Project Bundle Manager.w	
+++ b/inbuild/supervisor-module/Chapter 4/Project Bundle Manager.w	
@@ -100,7 +100,7 @@ inbuild_copy *ProjectBundleManager::claim_folder_as_copy(pathname *P) {
 					"the project directory '%S' contains a subdirectory called '%S', "
 					"which I don't recognise",
 					Pathnames::directory_name(P), subdir);
-				Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text));
+				Copies::attach_warning(C, CopyErrors::new_T(PROJECT_EXTRANEOUS_CE, -1, error_text));
 				DISCARD_TEXT(error_text)				
 			}
 			DISCARD_TEXT(subdir)
@@ -119,7 +119,7 @@ inbuild_copy *ProjectBundleManager::claim_folder_as_copy(pathname *P) {
 				"the project directory '%S' contains a file called '%S', "
 				"which I don't recognise",
 				Pathnames::directory_name(P), entry);
-			Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text));
+			Copies::attach_warning(C, CopyErrors::new_T(PROJECT_EXTRANEOUS_CE, -1, error_text));
 			DISCARD_TEXT(error_text)				
 		}
 	}
@@ -155,7 +155,7 @@ inbuild_copy *ProjectBundleManager::claim_folder_as_copy(pathname *P) {
 				"the 'Source' subdirectory of the project directory '%S' contains a "
 				"further subdirectory called '%S', but should not have further subdirectories",
 				Pathnames::directory_name(P), subdir);
-			Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text));
+			Copies::attach_warning(C, CopyErrors::new_T(PROJECT_EXTRANEOUS_CE, -1, error_text));
 			DISCARD_TEXT(error_text)
 			DISCARD_TEXT(subdir)
 		} else {
@@ -165,7 +165,7 @@ inbuild_copy *ProjectBundleManager::claim_folder_as_copy(pathname *P) {
 				"the 'Source' subdirectory of the project directory '%S' contains a "
 				"file called '%S', but should only contain the source text file 'story.ni'",
 				Pathnames::directory_name(P), entry);
-			Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text));
+			Copies::attach_warning(C, CopyErrors::new_T(PROJECT_EXTRANEOUS_CE, -1, error_text));
 			DISCARD_TEXT(error_text)				
 		}
 	}
@@ -185,7 +185,7 @@ inbuild_copy *ProjectBundleManager::claim_folder_as_copy(pathname *P) {
 					"the 'Build' subdirectory of the project directory '%S' contains a "
 					"further subdirectory called '%S', but should not have further subdirectories",
 					Pathnames::directory_name(P), subdir);
-				Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text));
+				Copies::attach_warning(C, CopyErrors::new_T(PROJECT_EXTRANEOUS_CE, -1, error_text));
 				DISCARD_TEXT(error_text)
 			}
 			DISCARD_TEXT(subdir)
@@ -210,7 +210,7 @@ inbuild_copy *ProjectBundleManager::claim_folder_as_copy(pathname *P) {
 					"the 'Index' subdirectory of the project directory '%S' contains a "
 					"further subdirectory called '%S', but can only have one, 'Details'",
 					Pathnames::directory_name(P), subdir);
-				Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text));
+				Copies::attach_warning(C, CopyErrors::new_T(PROJECT_EXTRANEOUS_CE, -1, error_text));
 				DISCARD_TEXT(error_text)
 			}
 			DISCARD_TEXT(subdir)
@@ -232,7 +232,7 @@ inbuild_copy *ProjectBundleManager::claim_folder_as_copy(pathname *P) {
 					"the 'Index' subdirectory of the project directory '%S' contains a "
 					"file called '%S', but can only contain HTML and XML files",
 					Pathnames::directory_name(P), entry);
-				Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text));
+				Copies::attach_warning(C, CopyErrors::new_T(PROJECT_EXTRANEOUS_CE, -1, error_text));
 				DISCARD_TEXT(error_text)
 			}
 		}
diff --git a/inform7/Figures/memory-diagnostics.txt b/inform7/Figures/memory-diagnostics.txt
index d1fc21936..eaa4a4758 100644
--- a/inform7/Figures/memory-diagnostics.txt
+++ b/inform7/Figures/memory-diagnostics.txt
@@ -1,10 +1,10 @@
 Total memory consumption was 143647K = 140 MB
 
- ---- was used for 2172492 objects, in 383444 frames in 0 x 800K = 0K = 0 MB:
+ ---- was used for 2172557 objects, in 383509 frames in 0 x 800K = 0K = 0 MB:
 
     29.4%  inter_tree_node_array                    60 x 8192 = 491520 objects, 43255680 bytes
     19.9%  text_stream_array                        5208 x 100 = 520800 objects, 29331456 bytes
-    18.1%  linked_list                              47752 objects, 26741120 bytes
+    18.2%  linked_list                              47817 objects, 26777520 bytes
      9.8%  inter_symbol_array                       136 x 1024 = 139264 objects, 14487808 bytes
      9.5%  inter_error_stash_array                  107 x 1024 = 109568 objects, 14028128 bytes
      7.2%  parse_node                               134069 objects, 10725520 bytes
@@ -125,8 +125,8 @@ Total memory consumption was 143647K = 140 MB
      ----  heading_tree                             33 objects, 10296 bytes
      ----  stopwatch_timer                          126 objects, 10080 bytes
      ----  understanding_item_array                 4 x 100 = 400 objects, 9728 bytes
+     ----  inbuild_copy                             65 objects, 9360 bytes
      ----  inbuild_work                             142 objects, 9088 bytes
-     ----  inbuild_copy                             65 objects, 8840 bytes
      ----  equation_node                            68 objects, 7616 bytes
      ----  shared_variable_array                    1 x 100 objects, 7232 bytes
      ----  determiner                               22 objects, 7216 bytes
@@ -285,5 +285,5 @@ Total memory consumption was 143647K = 140 MB
      ----  code generation workspace for objects    3552 bytes in 19 claims
      0.1%  emitter array storage                    290688 bytes in 2079 claims
 
--139.-5% was overhead - -205281376 bytes = -200470K = -195 MB
+-139.-5% was overhead - -205318296 bytes = -200506K = -195 MB
 
diff --git a/inform7/Figures/timings-diagnostics.txt b/inform7/Figures/timings-diagnostics.txt
index 33f77fbe8..89b325bf6 100644
--- a/inform7/Figures/timings-diagnostics.txt
+++ b/inform7/Figures/timings-diagnostics.txt
@@ -1,11 +1,11 @@
 100.0% in inform7 run
-     67.1% in compilation to Inter
-         45.1% in //Sequence::undertake_queued_tasks//
+     67.2% in compilation to Inter
+         45.4% in //Sequence::undertake_queued_tasks//
           4.8% in //MajorNodes::pre_pass//
           3.3% in //MajorNodes::pass_1//
           1.8% in //ImperativeDefinitions::assess_all//
-          1.4% in //RTKindConstructors::compile//
-          1.4% in //RTPhrasebook::compile_entries//
+          1.5% in //RTKindConstructors::compile//
+          1.5% in //RTPhrasebook::compile_entries//
           1.1% in //Sequence::lint_inter//
           0.3% in //CompletionModule::compile//
           0.3% in //ImperativeDefinitions::compile_first_block//
@@ -13,11 +13,11 @@
           0.3% in //Sequence::undertake_queued_tasks//
           0.3% in //Sequence::undertake_queued_tasks//
           0.3% in //World::stage_V//
-          5.6% not specifically accounted for
-     26.8% in running Inter pipeline
-          8.9% in step 14/15: generate inform6 -> auto.inf
-          7.0% in step 5/15: load-binary-kits
-          5.5% in step 6/15: make-synoptic-module
+          5.2% not specifically accounted for
+     26.6% in running Inter pipeline
+          8.6% in step 14/15: generate inform6 -> auto.inf
+          7.1% in step 5/15: load-binary-kits
+          5.6% in step 6/15: make-synoptic-module
           1.8% in step 9/15: make-identifiers-unique
           0.3% in step 12/15: eliminate-redundant-operations
           0.3% in step 4/15: compile-splats
diff --git a/inform7/core-module/Chapter 2/Problems With Source Text.w b/inform7/core-module/Chapter 2/Problems With Source Text.w
index 42f318a3a..b695f4002 100644
--- a/inform7/core-module/Chapter 2/Problems With Source Text.w	
+++ b/inform7/core-module/Chapter 2/Problems With Source Text.w	
@@ -62,13 +62,6 @@ void SourceProblems::issue_problems_arising(inbuild_copy *C) {
 					"which I don't recognise (which is not fine). Specifically, %2.");
 				Problems::issue_problem_end();
 				break;
-			case PROJECT_MALFORMED_CE:
-				Problems::quote_stream(1, CE->details);
-				StandardProblems::handmade_problem(Task::syntax_tree(), _p_(Untestable));
-				Problems::issue_problem_segment(
-					"This project seems to be malformed. Specifically, %1.");
-				Problems::issue_problem_end();
-				break;
 			case METADATA_MALFORMED_CE:
 			case MALFORMED_LICENCE_CE:
 				if (CE->copy->found_by) {
@@ -592,6 +585,18 @@ void SourceProblems::issue_problems_arising(inbuild_copy *C) {
 			default: internal_error("an unknown error occurred");
 		}
 	}
+	LOOP_OVER_LINKED_LIST(CE, copy_error, C->warnings) {
+		switch (CE->error_category) {
+			case PROJECT_EXTRANEOUS_CE:
+				Problems::quote_stream(1, CE->details);
+				StandardProblems::handmade_warning(Task::syntax_tree(), _p_(Untestable));
+				Problems::issue_problem_segment(
+					"This project contains some unexpected files or directories. Specifically, %1.");
+				Problems::issue_warning_end();
+				break;
+		}
+	}
+	C->warnings = NULL;
 }
 
 void SourceProblems::quote_genre(int N, copy_error *CE) {
diff --git a/inform7/core-module/Chapter 2/Using Problems.w b/inform7/core-module/Chapter 2/Using Problems.w
index 0e1a20c3b..82e670f84 100644
--- a/inform7/core-module/Chapter 2/Using Problems.w	
+++ b/inform7/core-module/Chapter 2/Using Problems.w	
@@ -42,7 +42,7 @@ void UsingProblems::end_problems_report(OUTPUT_STREAM) {
 void UsingProblems::final_report(int disaster_struck, int problems_count) {
 	int total_words = 0;
 
-	if (problem_count > 0) {
+	if (problems_count > 0) {
 		if (problems_file_active) ProblemBuffer::redirect_problem_stream(problems_file);
 		Problems::issue_problem_begin(Task::syntax_tree(), "*");
 		if (disaster_struck) @
diff --git a/services/problems-module/Chapter 2/Problems, Level 2.w b/services/problems-module/Chapter 2/Problems, Level 2.w
index b891aa740..01b2c6b59 100644
--- a/services/problems-module/Chapter 2/Problems, Level 2.w	
+++ b/services/problems-module/Chapter 2/Problems, Level 2.w	
@@ -72,11 +72,12 @@ the two possibilities.
 
 =
 int	do_not_locate_problems = FALSE;
+int warning_count = 0;
 
 void Problems::show_problem_location(parse_node_tree *T) {
 	parse_node *problem_headings[NO_HEADING_LEVELS];
 	int i, f = FALSE;
-	if (problem_count == 0) {
+	if (problem_count + warning_count == 0) {
 		#ifdef FIRST_PROBLEMS_CALLBACK
 		FIRST_PROBLEMS_CALLBACK(problems_file);
 		#endif
@@ -315,8 +316,6 @@ Warnings are almost identically handled. Inform traditionally avoided warnings,
 but we're finally giving way on that.
 
 =
-int warning_count = 0;
-
 int Problems::warnings_occurred(void) {
 	return (warning_count > 0)?TRUE:FALSE;
 }