diff --git a/Makefile b/Makefile index fe22d4f1b..c6fb868e0 100644 --- a/Makefile +++ b/Makefile @@ -3,11 +3,13 @@ TOOL_SOURCES := tool/pubspec.lock $(shell find tool -name '*.dart') BUILD_SNAPSHOT := $(BUILD_DIR)/build.dart.snapshot TEST_SNAPSHOT := $(BUILD_DIR)/test.dart.snapshot +DART_FLAGS := --old_gen_heap_size=1024 + default: book clox jlox # Run dart pub get on tool directory. get: - @ cd ./tool; dart pub get + @ cd ./tool; dart $(DART_FLAGS) pub get # Remove all build outputs and intermediate files. clean: @@ -16,46 +18,46 @@ clean: # Build the site. book: $(BUILD_SNAPSHOT) - @ dart $(BUILD_SNAPSHOT) + @ dart $(DART_FLAGS) $(BUILD_SNAPSHOT) # Run a local development server for the site that rebuilds automatically. serve: $(BUILD_SNAPSHOT) - @ dart $(BUILD_SNAPSHOT) --serve + @ dart $(DART_FLAGS) $(BUILD_SNAPSHOT) --serve $(BUILD_SNAPSHOT): $(TOOL_SOURCES) @ mkdir -p build @ echo "Compiling Dart snapshot..." - @ dart --snapshot=$@ --snapshot-kind=app-jit tool/bin/build.dart >/dev/null + @ dart $(DART_FLAGS) --snapshot=$@ --snapshot-kind=app-jit tool/bin/build.dart >/dev/null # Run the tests for the final versions of clox and jlox. test: debug jlox $(TEST_SNAPSHOT) - @- dart $(TEST_SNAPSHOT) clox - @ dart $(TEST_SNAPSHOT) jlox + @- dart $(DART_FLAGS) $(TEST_SNAPSHOT) clox + @ dart $(DART_FLAGS) $(TEST_SNAPSHOT) jlox # Run the tests for the final version of clox. test_clox: debug $(TEST_SNAPSHOT) - @ dart $(TEST_SNAPSHOT) clox + @ dart $(DART_FLAGS) $(TEST_SNAPSHOT) clox # Run the tests for the final version of jlox. test_jlox: jlox $(TEST_SNAPSHOT) - @ dart $(TEST_SNAPSHOT) jlox + @ dart $(DART_FLAGS) $(TEST_SNAPSHOT) jlox # Run the tests for every chapter's version of clox. test_c: debug c_chapters $(TEST_SNAPSHOT) - @ dart $(TEST_SNAPSHOT) c + @ dart $(DART_FLAGS) $(TEST_SNAPSHOT) c # Run the tests for every chapter's version of jlox. test_java: jlox java_chapters $(TEST_SNAPSHOT) - @ dart $(TEST_SNAPSHOT) java + @ dart $(DART_FLAGS) $(TEST_SNAPSHOT) java # Run the tests for every chapter's version of clox and jlox. test_all: debug jlox c_chapters java_chapters compile_snippets $(TEST_SNAPSHOT) - @ dart $(TEST_SNAPSHOT) all + @ dart $(DART_FLAGS) $(TEST_SNAPSHOT) all $(TEST_SNAPSHOT): $(TOOL_SOURCES) @ mkdir -p build @ echo "Compiling Dart snapshot..." - @ dart --snapshot=$@ --snapshot-kind=app-jit tool/bin/test.dart clox >/dev/null + @ dart $(DART_FLAGS) --snapshot=$@ --snapshot-kind=app-jit tool/bin/test.dart clox >/dev/null # Compile a debug build of clox. debug: @@ -193,14 +195,14 @@ diffs: split_chapters java_chapters @ -diff --new-file gen/chap29_superclasses/ gen/chap30_optimization/ > build/diffs/chap30_optimization.diff split_chapters: - @ dart tool/bin/split_chapters.dart + @ dart $(DART_FLAGS) tool/bin/split_chapters.dart compile_snippets: - @ dart tool/bin/compile_snippets.dart + @ dart $(DART_FLAGS) tool/bin/compile_snippets.dart # Generate the XML for importing into InDesign. xml: $(TOOL_SOURCES) - @ dart --enable-asserts tool/bin/build_xml.dart + @ dart $(DART_FLAGS) --enable-asserts tool/bin/build_xml.dart .PHONY: book c_chapters clean clox compile_snippets debug default diffs \ get java_chapters jlox serve split_chapters test test_all test_c test_java diff --git a/asset/index.scss b/asset/index.scss index 596902b6b..3214a26a7 100644 --- a/asset/index.scss +++ b/asset/index.scss @@ -142,7 +142,7 @@ a.action { small { font-size: 14px; padding: 4px; - color: hsla(0, 0, 100%, 0.7); + color: hsla(0, 0%, 100%, 0.7); transition: color 0.2s ease; } } diff --git a/asset/sass/chapter.scss b/asset/sass/chapter.scss index d946ebce2..4244ec09b 100644 --- a/asset/sass/chapter.scss +++ b/asset/sass/chapter.scss @@ -1,3 +1,5 @@ +@use "sass:math"; + article.chapter { h2 { font: 600 30px/24px $serif; @@ -192,8 +194,8 @@ article.chapter { @media only screen and (max-width: 630px) { article.chapter { h2 a::before, h3 a::before { - left: -($col / 2); - width: $col / 2; + left: -(math.div($col, 2)); + width: math.div($col, 2); } } } diff --git a/asset/style.scss b/asset/style.scss index 8998fa5ba..e06f1f08a 100644 --- a/asset/style.scss +++ b/asset/style.scss @@ -1,3 +1,5 @@ +@use "sass:math"; + @import 'sass/shared'; @import 'sass/chapter'; @import 'sass/contents'; @@ -210,7 +212,7 @@ nav.floating { } img { - padding: 110px $col/2 23px $col/2; + padding: 110px math.div($col, 2) 23px math.div($col, 2); } } @@ -332,7 +334,7 @@ pre { // If the chapter ends with code, don't overlap the challenges box. div.codehilite + div.challenges { - margin-top: $col / 2; + margin-top: math.div($col, 2); } article { @@ -751,13 +753,13 @@ footer { // The cut-off sizes here are based on trying to get 72 columns of code to fit. @media only screen and (max-width: 630px) { .page { - margin: 0 $col / 2; + margin: 0 math.div($col, 2); width: inherit; } nav.narrow { .prev, .next { - padding: 0 $col / 2; + padding: 0 math.div($col, 2); } } } diff --git a/shell.nix b/shell.nix new file mode 100644 index 000000000..b79aba69c --- /dev/null +++ b/shell.nix @@ -0,0 +1,20 @@ +# Using the NixOS 25.11 stable release for a consistent Dart 3 environment. +{ pkgs ? import (fetchTarball { + url = "https://github.com/NixOS/nixpkgs/archive/nixos-25.11.tar.gz"; + sha256 = "1hp1ddh41wrqxgrnfa79nsv7gzlbpwcqsijx7jw5b33wvk5ah5gb"; +}) {} }: + +pkgs.mkShell { + buildInputs = [ + pkgs.dart + pkgs.zulu + pkgs.gcc + pkgs.gnumake + pkgs.git + pkgs.glibcLocales + ]; + + shellHook = '' + export LANG=en_US.UTF-8 + ''; +} diff --git a/site/index.css b/site/index.css index 56437dd4c..0a129445f 100644 --- a/site/index.css +++ b/site/index.css @@ -56,8 +56,8 @@ body { .sign-up { padding: 12px; margin: 24px 0 24px 0; - background: #fcf6e8; - color: #bf9540; + background: hsl(40, 80%, 95%); + color: hsl(40, 50%, 50%); border-radius: 3px; } .sign-up form { @@ -68,8 +68,8 @@ body { font: 16px "Source Sans Pro", sans-serif; outline: none; border-radius: 3px; - border: solid 2px #ffd580; - color: #825e17; + border: solid 2px hsl(40, 100%, 75%); + color: hsl(40, 70%, 30%); height: 32px; } .sign-up input.email { @@ -83,15 +83,15 @@ body { font: 600 13px "Source Sans Pro", sans-serif; text-transform: uppercase; letter-spacing: 1px; - background: #ffbb33; + background: hsl(40, 100%, 60%); border: none; transition: background-color 0.2s ease; } .sign-up input.button:hover { - background: #ffd580; + background: hsl(40, 100%, 75%); } .sign-up input:focus { - border-color: #ffaa00; + border-color: hsl(40, 100%, 50%); } body, h1, h2, h3, h4, p, blockquote, code, ul, ol, dl, dd, img { @@ -99,20 +99,20 @@ body, h1, h2, h3, h4, p, blockquote, code, ul, ol, dl, dd, img { } body { - background: #29313d url("image/background.png") top center/100% auto no-repeat; + background: hsl(215, 20%, 20%) url("image/background.png") top center/100% auto no-repeat; color: #222; font: normal 16px/24px "Crimson", Georgia, serif; } a { - color: #1481b8; + color: hsl(200, 80%, 40%); text-decoration: none; border-bottom: solid 1px rgba(222, 233, 237, 0); transition: color 0.2s ease, border-color 0.4s ease; } a:hover { - color: #1481b8; + color: hsl(200, 80%, 40%); border-bottom: solid 1px #dee9ed; } @@ -125,8 +125,8 @@ article { header { margin: 0 0 48px 0; - color: #595959; - background: #f5f3f0; + color: hsl(40, 0%, 35%); + background: hsl(40, 20%, 95%); border-bottom: solid 1px #dad8d6; } @@ -165,7 +165,7 @@ p + p { padding: 12px 12px 8px 12px; height: 244px; box-sizing: border-box; - background: #eef4f7; + background: hsl(195, 35%, 95%); background-size: cover; background-position: left; color: #444; @@ -223,7 +223,7 @@ a.action { padding: 4px 0; text-align: center; border-radius: 3px; - background: #1481b8; + background: hsl(200, 80%, 40%); transition: background-color 0.2s ease, color 0.2s ease; font: 400 17px/24px "Source Sans Pro", sans-serif; color: white; @@ -231,12 +231,12 @@ a.action { a.action small { font-size: 14px; padding: 4px; - color: rgba(255, 255, 255, 0.7); + color: hsla(0, 0%, 100%, 0.7); transition: color 0.2s ease; } a.action:hover { - background-color: #2badee; + background-color: hsl(200, 85%, 55%); } a.action:hover small { color: white; @@ -252,7 +252,7 @@ img.author { width: 240px; margin: 0 12px 0 -12px; padding: 12px; - background: #f5f3f0; + background: hsl(40, 20%, 95%); border-radius: 3px; } @@ -263,8 +263,8 @@ div.author { footer { position: relative; - border-top: solid 1px #dee9ed; - color: #7aa0b8; + border-top: solid 1px hsl(195, 30%, 90%); + color: hsl(203, 30%, 60%); font: 400 15px "Source Sans Pro", sans-serif; text-align: center; margin: 12px 0 36px 0; @@ -278,19 +278,15 @@ footer a, footer a:hover { main { margin: 0 24px; } - header { margin-bottom: 24px; } - img.big { display: none; } - img.small { display: block; } - div.intro { display: block; } @@ -302,38 +298,30 @@ footer a, footer a:hover { display: block; margin: 24px 0 24px 0; } - .format { margin-bottom: 12px; height: auto; background-blend-mode: lighten; } - .format-info { display: block; width: 100%; } - .format.print { background-color: #a6a29f; } - .format.ebook { background-color: #97a2aa; } - .format.pdf { background-color: #cfccca; } - .format.web { background-color: #d6dbd3; } - img.author { float: none; } - div.author { margin: 0 0 0 0; } diff --git a/site/style.css b/site/style.css index 270a593ea..f34c83f34 100644 --- a/site/style.css +++ b/site/style.css @@ -134,8 +134,8 @@ article.chapter .challenges > blockquote::before, article.chapter .challenges > content: none; } article.chapter .challenges aside code, article.chapter .challenges aside .codehilite, article.chapter .design-note aside code, article.chapter .design-note aside .codehilite { - color: #595959; - background: #faf8f5; + color: hsl(40, 0%, 35%); + background: hsl(40, 30%, 97%); } article.chapter .challenges *:last-child, article.chapter .design-note *:last-child { margin-bottom: 0; @@ -145,16 +145,16 @@ article.chapter .design-note .codehilite { margin: -12px 0 -12px 0; } article.chapter .challenges { - background: #eef4f7; + background: hsl(195, 35%, 95%); } article.chapter .challenges code, article.chapter .challenges .codehilite { - background: #e4eef1; + background: hsl(195, 30%, 92%); } article.chapter .design-note { - background: #f6f8f2; + background: hsl(80, 30%, 96%); } article.chapter .design-note code, article.chapter .design-note .codehilite { - background: #eef1ea; + background: hsl(80, 20%, 93%); } article.chapter table { width: 100%; @@ -164,7 +164,7 @@ article.chapter table thead { font: 700 15px "Crimson", Georgia, serif; } article.chapter table td { - border-bottom: solid 1px #dee9ed; + border-bottom: solid 1px hsl(195, 30%, 90%); line-height: 22px; padding: 3px 0 0 0; margin: 0; @@ -179,10 +179,10 @@ article.chapter table td + td { padding-bottom: 4px; } article.chapter .challenges aside code, article.chapter .challenges aside .codehilite { - background: #e4eef1; + background: hsl(195, 30%, 92%); } article.chapter .design-note aside code, article.chapter .design-note aside .codehilite { - background: #eef1ea; + background: hsl(80, 20%, 93%); } } @media only screen and (max-width: 630px) { @@ -238,7 +238,7 @@ article.contents ul { article.contents li { padding: 12px 0 0 36px; font: normal 16px/24px "Source Sans Pro", sans-serif; - color: #7aa0b8; + color: hsl(203, 30%, 60%); list-style-type: none; } article.contents li .num { @@ -323,8 +323,8 @@ article.contents footer { .sign-up { padding: 12px; margin: 24px 0 24px 0; - background: #fcf6e8; - color: #bf9540; + background: hsl(40, 80%, 95%); + color: hsl(40, 50%, 50%); border-radius: 3px; } .sign-up form { @@ -335,8 +335,8 @@ article.contents footer { font: 16px "Source Sans Pro", sans-serif; outline: none; border-radius: 3px; - border: solid 2px #ffd580; - color: #825e17; + border: solid 2px hsl(40, 100%, 75%); + color: hsl(40, 70%, 30%); height: 32px; } .sign-up input.email { @@ -350,15 +350,15 @@ article.contents footer { font: 600 13px "Source Sans Pro", sans-serif; text-transform: uppercase; letter-spacing: 1px; - background: #ffbb33; + background: hsl(40, 100%, 60%); border: none; transition: background-color 0.2s ease; } .sign-up input.button:hover { - background: #ffd580; + background: hsl(40, 100%, 75%); } .sign-up input:focus { - border-color: #ffaa00; + border-color: hsl(40, 100%, 50%); } @font-face { @@ -421,15 +421,12 @@ body { color: #000 !important; background: none !important; } - nav, .sign-up { display: none; } - .page { margin: 0 !important; } - .codehilite { margin: 0 !important; background: none !important; @@ -472,29 +469,29 @@ body { } a { - color: #1481b8; + color: hsl(200, 80%, 40%); text-decoration: none; border-bottom: solid 1px rgba(222, 233, 237, 0); transition: color 0.2s ease, border-color 0.4s ease; } a:hover { - color: #1481b8; + color: hsl(200, 80%, 40%); border-bottom: solid 1px #dee9ed; } nav { font: 300 15px/24px "Source Sans Pro", sans-serif; - background: #29313d; - color: #4b6781; + background: hsl(215, 20%, 20%); + color: hsl(209, 26%, 40%); } nav a, nav h2 a { - color: #7aa0b8; + color: hsl(203, 30%, 60%); text-decoration: none; border-bottom: none; } nav a:hover { - color: #dee9ed; + color: hsl(195, 30%, 90%); text-decoration: none; border-bottom: none; } @@ -507,22 +504,22 @@ nav h2 { font: 400 16px/24px "Source Sans Pro", sans-serif; text-transform: uppercase; letter-spacing: 1px; - color: #7aa0b8; + color: hsl(203, 30%, 60%); } nav h3 { font: 400 18px/24px "Source Sans Pro", sans-serif; - color: #7aa0b8; + color: hsl(203, 30%, 60%); } nav h2 small, nav h3 small { float: right; font-size: 16px; - color: #4b6781; + color: hsl(209, 26%, 40%); } nav ol, nav ul { margin: 6px 0 3px 0; padding: 6px 0 4px 24px; - border-top: solid 1px #3b4b5e; - border-bottom: solid 1px #3b4b5e; + border-top: solid 1px hsl(212, 23%, 30%); + border-bottom: solid 1px hsl(212, 23%, 30%); } nav ul { list-style-type: none; @@ -530,18 +527,18 @@ nav ul { } nav hr { border: none; - border-top: solid 1px #3b4b5e; + border-top: solid 1px hsl(212, 23%, 30%); margin: 6px 0 0 0; padding: 0 0 3px 0; } nav li small { float: right; font-size: 14px; - color: #4b6781; + color: hsl(209, 26%, 40%); } nav li.divider { margin: 5px 0 7px 0; - border-top: solid 1px #3b4b5e; + border-top: solid 1px hsl(212, 23%, 30%); } nav li.end-part { font-size: 12px; @@ -589,7 +586,7 @@ nav.floating #expand-nav { display: block; font-size: 20px; text-align: center; - color: #4b6781; + color: hsl(209, 26%, 40%); cursor: pointer; transition: padding 0.2s ease, margin 0.2s ease, color 0.2s ease; } @@ -597,7 +594,7 @@ nav.floating #expand-nav, nav.floating #expand-nav:hover { border-bottom: none; } nav.floating #expand-nav:hover { - color: #dee9ed; + color: hsl(195, 30%, 90%); } nav.floating .expandable { overflow: hidden; @@ -685,12 +682,12 @@ strong code { } a code { - color: #1481b8; + color: hsl(200, 80%, 40%); } .codehilite { - color: #595959; - background: #faf8f5; + color: hsl(40, 0%, 35%); + background: hsl(40, 30%, 97%); border-radius: 3px; padding: 12px; margin: -12px; @@ -730,7 +727,7 @@ article .number { left: 624px; z-index: 1; font: 300 96px "Source Sans Pro", sans-serif; - color: #dee9ed; + color: hsl(195, 30%, 90%); } article p { margin: 24px 0; @@ -752,7 +749,7 @@ aside { right: -336px; width: 288px; font: normal 14px/20px "Crimson", Georgia, serif; - border-top: solid 1px #dee9ed; + border-top: solid 1px hsl(195, 30%, 90%); } aside p { margin: 20px 0; @@ -795,7 +792,7 @@ aside blockquote p { aside.bottom { border-top: none; - border-bottom: solid 1px #dee9ed; + border-bottom: solid 1px hsl(195, 30%, 90%); } blockquote { @@ -806,7 +803,7 @@ blockquote::before, blockquote::after { position: absolute; top: -20px; font: italic 72px "Crimson", Georgia, serif; - color: #dee9ed; + color: hsl(195, 30%, 90%); } blockquote::before { content: "“"; @@ -819,7 +816,7 @@ blockquote::after { blockquote p { margin: 0 48px; font: italic 24px/36px "Crimson", Georgia, serif; - color: #5985a6; + color: hsl(206, 30%, 50%); } blockquote p em { font-style: normal; @@ -827,13 +824,13 @@ blockquote p em { blockquote cite { display: block; text-align: right; - color: #7aa0b8; + color: hsl(203, 30%, 60%); font-style: normal; font-size: 18px; } blockquote cite::before { content: "— "; - color: #dee9ed; + color: hsl(195, 30%, 90%); } blockquote cite em { font-style: italic; @@ -841,8 +838,8 @@ blockquote cite em { footer { position: relative; - border-top: solid 1px #dee9ed; - color: #7aa0b8; + border-top: solid 1px hsl(195, 30%, 90%); + color: hsl(203, 30%, 60%); font: 400 15px "Source Sans Pro", sans-serif; text-align: center; margin: 48px 0; @@ -862,7 +859,7 @@ footer .next { letter-spacing: 1px; } footer .next:hover { - color: #004466; + color: hsl(200, 100%, 20%); border: none; } @@ -909,35 +906,35 @@ footer .next:hover { color: #797978; } .codehilite .k { - color: #0099e6; + color: hsl(200, 100%, 45%); } .codehilite .n { - color: #dd713c; + color: hsl(20, 70%, 55%); } .codehilite .s { - color: #c38e22; + color: hsl(40, 70%, 45%); } .codehilite .e { - color: #e8ba30; + color: hsl(45, 80%, 55%); } .codehilite .c { color: #aaa9a7; } .codehilite .a { - color: #9966cc; + color: hsl(270, 50%, 60%); } .codehilite .i { - color: #1b6e98; + color: hsl(200, 70%, 35%); } .codehilite .t { - color: #00a4b3; + color: hsl(185, 100%, 35%); } .codehilite .insert { margin: -2px -12px; padding: 2px 10px; border-left: solid 2px #dad8d6; border-right: solid 2px #dad8d6; - background: #f5f3f0; + background: hsl(40, 20%, 95%); } .codehilite .delete { margin: -2px -12px; @@ -956,30 +953,26 @@ footer .next:hover { margin: -2px -1px; padding: 2px 1px; border-radius: 2px; - background: #f5f3f0; - color: #595959; + background: hsl(40, 20%, 95%); + color: hsl(40, 0%, 35%); } @media only screen and (max-width: 1344px) { nav.wide { display: none; } - nav.floating { display: block; } - body { margin: 0 24px; } - .page { position: relative; width: inherit; max-width: 912px; margin: 0 auto; } - article { width: inherit; margin-right: 336px; @@ -999,20 +992,16 @@ footer .next:hover { body { margin: 0; } - nav.floating { display: none; } - nav.narrow { display: block; } - .page { margin: 0 48px; width: inherit; } - article { margin: 0; } @@ -1020,12 +1009,11 @@ footer .next:hover { width: inherit; max-width: 100%; } - aside { position: inherit; right: inherit; width: inherit; - border-bottom: solid 1px #dee9ed; + border-bottom: solid 1px hsl(195, 30%, 90%); } aside p:first-child { margin-top: 8px; @@ -1044,19 +1032,15 @@ footer .next:hover { aside img.above { position: relative; } - aside + div.codehilite { margin-top: 12px; } - div.codehilite + aside { margin-top: 24px; } - .source-file { display: none; } - .source-file-narrow { display: block; } @@ -1066,7 +1050,6 @@ footer .next:hover { margin: 0 24px; width: inherit; } - nav.narrow .prev, nav.narrow .next { padding: 0 24px; } @@ -1076,15 +1059,12 @@ footer .next:hover { font-size: 15px; line-height: 22px; } - .small-caps { font-size: 12px; } - .scrim { background: url("rows-22.png"); } - nav.narrow img { padding: 9px 0 1px 0; height: 27px; @@ -1092,7 +1072,6 @@ footer .next:hover { nav.narrow .prev, nav.narrow .next { top: 11px; } - article h1 { font-size: 36px; padding: 100px 0 14px 0; @@ -1112,7 +1091,6 @@ footer .next:hover { margin: 22px 0; padding: 0 0 0 22px; } - blockquote { margin: 27px 0 28px 0; } @@ -1125,7 +1103,6 @@ footer .next:hover { font-size: 20px; line-height: 33px; } - footer .next { font-size: 15px; } diff --git a/tool/bin/build.dart b/tool/bin/build.dart index 17245bb35..f2bcf88e1 100644 --- a/tool/bin/build.dart +++ b/tool/bin/build.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:glob/glob.dart'; +import 'package:glob/list_local_fs.dart'; import 'package:mime_type/mime_type.dart'; import 'package:path/path.dart' as p; import 'package:sass/sass.dart' as sass; @@ -45,7 +46,7 @@ void _buildPages({bool skipUpToDate = false}) { var book = Book(); var mustache = Mustache(); - DateTime dependenciesModified; + DateTime? dependenciesModified; if (skipUpToDate) { dependenciesModified = _mostRecentlyModified( ["asset/mustache/*.html", "c/*.{c,h}", "java/**.java"]); @@ -71,7 +72,7 @@ void _buildPages({bool skipUpToDate = false}) { } List _buildPage(Book book, Mustache mustache, Page page, - {DateTime dependenciesModified}) { + {DateTime? dependenciesModified}) { // See if the HTML is up to date. if (dependenciesModified != null && _isUpToDate(page.htmlPath, page.markdownPath, dependenciesModified)) { @@ -143,7 +144,8 @@ void _buildSass({bool skipUpToDate = false}) { var cssPath = p.join("site", p.basenameWithoutExtension(source.path) + ".css"); - if (skipUpToDate && _isUpToDate(cssPath, scssPath, moduleModified)) { + if (skipUpToDate && moduleModified != null && + _isUpToDate(cssPath, scssPath, moduleModified)) { continue; } @@ -170,7 +172,7 @@ Future _runServer() async { try { var contents = await File(p.join("site", filePath)).readAsBytes(); return shelf.Response.ok(contents, headers: { - HttpHeaders.contentTypeHeader: mimeFromExtension(extension) + HttpHeaders.contentTypeHeader: mimeFromExtension(extension) ?? (throw ArgumentError('No mime type exists for $extension')) }); } on FileSystemException { print( @@ -196,12 +198,12 @@ bool _isUpToDate( } /// The most recently modified time of all files that match [globs]. -DateTime _mostRecentlyModified(List globs) { - DateTime latest; +DateTime? _mostRecentlyModified(List globs) { + DateTime? latest; for (var glob in globs) { for (var entry in Glob(glob).listSync()) { - if (entry is File) { - var modified = entry.lastModifiedSync(); + if (entry case File file) { + var modified = file.lastModifiedSync(); if (latest == null || modified.isAfter(latest)) latest = modified; } } diff --git a/tool/bin/compile_snippets.dart b/tool/bin/compile_snippets.dart index 02b3cc5fc..75c81e429 100644 --- a/tool/bin/compile_snippets.dart +++ b/tool/bin/compile_snippets.dart @@ -305,7 +305,7 @@ Future main(List arguments) async { var tags = chapter.codeTags; var tagNames = _chapterTags[chapterName]; - if (tagNames.isNotEmpty) { + if (tagNames?.isNotEmpty ?? false) { tags = tagNames.map((name) => book.findTag(chapter, name)); } else { print("Warning, no in-chapter snippets for '$chapterName'"); diff --git a/tool/bin/test.dart b/tool/bin/test.dart index cb1981cb9..006ef4bb3 100644 --- a/tool/bin/test.dart +++ b/tool/bin/test.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:args/args.dart'; import 'package:glob/glob.dart'; +import 'package:glob/list_local_fs.dart'; import 'package:path/path.dart' as p; import 'package:tool/src/term.dart' as term; @@ -22,10 +23,10 @@ var _failed = 0; var _skipped = 0; var _expectations = 0; -Suite _suite; -String _filterPath; -String _customInterpreter; -List _customArguments; +late Suite _suite; +String? _filterPath; +String? _customInterpreter; +List? _customArguments; final _allSuites = {}; final _cSuites = []; @@ -60,7 +61,7 @@ void main(List arguments) { } var suite = options.rest[0]; - if (options.rest.length == 2) _filterPath = arguments[1]; + if (options.rest.length == 2) _filterPath = options.rest[1]; if (options.wasParsed("interpreter")) { _customInterpreter = options["interpreter"] as String; @@ -110,7 +111,7 @@ void _runSuites(List names) { } bool _runSuite(String name) { - _suite = _allSuites[name]; + _suite = _allSuites[name] ?? (throw ArgumentError('No suite named "$name"')); _passed = 0; _failed = 0; @@ -144,9 +145,8 @@ void _runTest(String path) { // Check if we are just running a subset of the tests. if (_filterPath != null) { var thisTest = p.posix.relative(path, from: "test"); - if (!thisTest.startsWith(_filterPath)) return; + if (!thisTest.startsWith(_filterPath ?? (throw StateError('_filterPath is null')))) return; } - // Update the status line. var grayPath = term.gray("($path)"); term.writeLine("Passed: ${term.green(_passed)} " @@ -191,7 +191,7 @@ class Test { final _expectedErrors = {}; /// The expected runtime error message or `null` if there should not be one. - String _expectedRuntimeError; + String? _expectedRuntimeError; /// If there is an expected runtime error, the line it should occur on. int _runtimeErrorLine = 0; @@ -207,7 +207,7 @@ class Test { // Get the path components. var parts = _path.split("/"); var subpath = ""; - String state; + String? state; // Figure out the state of the test. We don't break out of this loop because // we want lines for more specific paths to override more general ones. @@ -237,7 +237,7 @@ class Test { match = _expectedOutputPattern.firstMatch(line); if (match != null) { - _expectedOutput.add(ExpectedOutput(lineNum, match[1])); + _expectedOutput.add(ExpectedOutput(lineNum, match[1] ?? (throw ArgumentError('Expected output missing for match at line $lineNum')))); _expectations++; continue; } @@ -273,7 +273,7 @@ class Test { match = _expectedRuntimeErrorPattern.firstMatch(line); if (match != null) { _runtimeErrorLine = lineNum; - _expectedRuntimeError = match[1]; + _expectedRuntimeError = match[1] ?? (throw ArgumentError('Expected runtime error missing for match at line $lineNum')); // If we expect a runtime error, it should exit with EX_SOFTWARE. _expectedExitCode = 70; _expectations++; @@ -327,7 +327,7 @@ class Test { } // Make sure the stack trace has the right line. - RegExpMatch match; + RegExpMatch? match; var stackLines = errorLines.sublist(1); for (var line in stackLines) { match = _stackTracePattern.firstMatch(line); @@ -337,7 +337,7 @@ class Test { if (match == null) { fail("Expected stack trace and got:", stackLines); } else { - var stackLine = int.parse(match[1]); + var stackLine = int.parse(match[1] ?? (throw ArgumentError('Stack line missing in match'))); if (stackLine != _runtimeErrorLine) { fail("Expected runtime error on line $_runtimeErrorLine " "but was on line $stackLine."); @@ -422,7 +422,7 @@ class Test { } } - void fail(String message, [List lines]) { + void fail(String message, [List? lines]) { _failures.add(message); if (lines != null) _failures.addAll(lines); } diff --git a/tool/lib/src/book.dart b/tool/lib/src/book.dart index b01bf5333..23f810922 100644 --- a/tool/lib/src/book.dart +++ b/tool/lib/src/book.dart @@ -6,6 +6,7 @@ import 'source_file_parser.dart'; import 'text.dart'; import 'package:glob/glob.dart'; +import 'package:glob/list_local_fs.dart'; import 'package:path/path.dart' as p; const _tableOfContents = { @@ -82,14 +83,14 @@ class Book { } // There is no part page for the frontmatter. - Page partPage; + Page? partPage; if (part != "") { partPage = Page(part, null, partNumber, pages.length); pages.add(partPage); parts.add(partPage); } - for (var chapter in _tableOfContents[part]) { + for (var chapter in (_tableOfContents[part] ?? (throw ArgumentError('No table of contents for part "$part"')))) { var chapterNumber = ""; if (inMatter) { // Front- and backmatter chapters are specially numbered. @@ -127,8 +128,7 @@ class Book { snippet.addLine(lineIndex, line); if (line.end != null) { - var endSnippet = _snippets.putIfAbsent( - line.end, () => Snippet(sourceFile, line.end)); + var endSnippet = _snippets.putIfAbsent(line.end!, () => Snippet(sourceFile, line.end!)); endSnippet.removeLine(lineIndex, line); } @@ -153,19 +153,19 @@ class Book { pages.firstWhere((page) => page.numberString == number); /// Gets the [Page] [offset] pages before or after this one. - Page adjacentPage(Page start, int offset) { + Page? adjacentPage(Page start, int offset) { var index = pages.indexOf(start) + offset; if (index < 0 || index >= pages.length) return null; return pages[index]; } - Snippet findSnippet(CodeTag tag) => _snippets[tag]; + Snippet? findSnippet(CodeTag tag) => _snippets[tag]; /// Gets the last snippet that appears in [page]. /// /// Note: Not very fast. - Snippet lastSnippet(Page page) { - Snippet last; + Snippet? lastSnippet(Page page) { + Snippet? last; for (var snippet in _snippets.values) { if (snippet.tag.chapter != page) continue; if (last == null || snippet.tag > last.tag) last = snippet; @@ -209,7 +209,7 @@ class SourceLine { /// The last snippet where this line is removed, or null if the line reaches /// the end of the book. - final CodeTag end; + final CodeTag? end; SourceLine(this.text, this.location, this.start, this.end); @@ -219,7 +219,7 @@ class SourceLine { if (tag < start) return false; // If we are past the snippet where it is removed. - if (end != null && tag >= end) return false; + if (end != null && tag >= end!) return false; return true; } diff --git a/tool/lib/src/location.dart b/tool/lib/src/location.dart index 8ab01fe32..32f623395 100644 --- a/tool/lib/src/location.dart +++ b/tool/lib/src/location.dart @@ -1,10 +1,10 @@ /// The context in which a line of code appears. The chain of types and /// functions it's in. class Location { - final Location parent; + final Location? parent; final String kind; - String _name; - final String signature; + String? _name; + final String? signature; /// If [kind] is "method" or "function" then this tracks where we are /// declaring or defining the function. @@ -13,9 +13,9 @@ class Location { Location(this.parent, this.kind, this._name, {this.signature, this.isFunctionDeclaration = false}); - String get name => _name; + String? get name => _name; - set name(String value) { + set name(String? value) { // Can only set the name if it's an unnamed typedef. assert(_name == null); _name = value; @@ -27,7 +27,7 @@ class Location { const {"constructor", "function", "method"}.contains(kind); int get depth { - var current = this; + var current = this as Location?; var result = 0; while (current != null) { result++; @@ -45,13 +45,13 @@ class Location { /// Generates a string of HTML that describes a snippet at this location, /// when following the [preceding] location. - String toHtml(Location preceding, List removed) { + String? toHtml(Location preceding, List removed) { if (kind == "new") return "create new file"; if (kind == "top") return "add to top of file"; // Note: The order of these is highly significant. if (kind == "class" && parent?.kind == "class") { - return "nest inside class ${parent.name}"; + return "nest inside class ${(parent ?? (throw StateError('Parent is null'))).name}"; } if (isFunction && preceding == this) { @@ -104,13 +104,13 @@ class Location { /// /// This is similar to [toHtml] but uses different tags and places the /// signatures inside the tags instead of outside. - String toXml(Location preceding, List removed) { + String? toXml(Location preceding, List removed) { if (kind == "new") return "create new file"; if (kind == "top") return "add to top of file"; // Note: The order of these is highly significant. if (kind == "class" && parent?.kind == "class") { - return "nest inside class ${parent.name}"; + return "nest inside class ${(parent ?? (throw StateError('Parent is null'))).name}"; } if (isFunction && preceding == this) { @@ -174,11 +174,11 @@ class Location { return other is Location && kind == other.kind && name == other.name; } - int get hashCode => kind.hashCode ^ name.hashCode; + int get hashCode => kind.hashCode ^ (name?.hashCode ?? 0); /// Discard as many children as needed to get to [depth] parents. Location popToDepth(int depth) { - var current = this; + var current = this as Location?; var locations = []; while (current != null) { locations.add(current); diff --git a/tool/lib/src/markdown/block_syntax.dart b/tool/lib/src/markdown/block_syntax.dart index 8ebd78770..3f71893aa 100644 --- a/tool/lib/src/markdown/block_syntax.dart +++ b/tool/lib/src/markdown/block_syntax.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:markdown/markdown.dart'; import '../format.dart'; @@ -20,7 +22,13 @@ class BookHeaderSyntax extends BlockSyntax { BookHeaderSyntax(this._page, this._format); Node parse(BlockParser parser) { - var header = _page.headers[parser.current]; + var header = _page.headers[parser.current.content]; + if (header == null) { + stderr.writeln("[ERR] Could not find header for line: '${parser.current.content}'"); + stderr.writeln("[ERR] Available headers: ${_page.headers.keys.join(', ')}"); + exit(1); + } + parser.advance(); if (_format.isPrint) { diff --git a/tool/lib/src/markdown/code_syntax.dart b/tool/lib/src/markdown/code_syntax.dart index fa0f54118..09ae70763 100644 --- a/tool/lib/src/markdown/code_syntax.dart +++ b/tool/lib/src/markdown/code_syntax.dart @@ -1,4 +1,5 @@ import 'package:markdown/markdown.dart'; +import 'package:markdown/src/line.dart'; import '../book.dart'; import '../code_tag.dart'; @@ -19,14 +20,14 @@ class HighlightedCodeBlockSyntax extends BlockSyntax { HighlightedCodeBlockSyntax(this._format); bool canParse(BlockParser parser) => - pattern.firstMatch(parser.current) != null; + pattern.firstMatch(parser.current.content) != null; - List parseChildLines(BlockParser parser) { - var childLines = []; + List parseChildLines(BlockParser parser) { + var childLines = []; parser.advance(); while (!parser.isDone) { - var match = pattern.firstMatch(parser.current); + var match = pattern.firstMatch(parser.current.content); if (match == null) { childLines.add(parser.current); parser.advance(); @@ -41,11 +42,17 @@ class HighlightedCodeBlockSyntax extends BlockSyntax { Node parse(BlockParser parser) { // Get the syntax identifier, if there is one. - var match = pattern.firstMatch(parser.current); - var indent = match[1].length; - var language = match[2]; + var match = pattern.firstMatch(parser.current.content); + if (match == null) throw StateError('Match missing for pattern at current line'); + + var indent = (match[1] ?? (throw ArgumentError('Indent missing in match'))).length; + var language = match[2] ?? (throw ArgumentError('Language missing in match')); var childLines = parseChildLines(parser); + var childStrings = childLines.map((line) { + if (line == null) throw StateError('Line is null'); + return line.content; + }).toList(); String code; if (language == "text") { @@ -59,10 +66,10 @@ class HighlightedCodeBlockSyntax extends BlockSyntax { // https://html.spec.whatwg.org/#element-restrictions // Some snippets deliberately start with a newline which needs to be // preserved, so output an extra (discarded) newline in that case. - if (_format.isWeb && childLines.first.isEmpty) buffer.writeln(); + if (_format.isWeb && childStrings.first.isEmpty) buffer.writeln(); } - for (var line in childLines) { + for (var line in childStrings) { // Strip off any leading indentation. if (line.length > indent) line = line.substring(indent); checkLineLength(line); @@ -80,7 +87,7 @@ class HighlightedCodeBlockSyntax extends BlockSyntax { code = buffer.toString(); } else { - code = formatCode(language, childLines, _format, indent: indent); + code = formatCode(language, childStrings, _format, indent: indent); } if (_format.isPrint) { @@ -115,15 +122,17 @@ class CodeTagBlockSyntax extends BlockSyntax { RegExp get pattern => _startPattern; bool canParse(BlockParser parser) => - pattern.firstMatch(parser.current) != null; + pattern.firstMatch(parser.current.content) != null; Node parse(BlockParser parser) { - var match = pattern.firstMatch(parser.current); - var name = match[1]; + var match = pattern.firstMatch(parser.current.content); + if (match == null) throw StateError('Match missing for pattern at current line'); + + var name = match[1] ?? (throw ArgumentError('Name missing in match')); parser.advance(); var codeTag = _page.findCodeTag(name); - String snippet; + String? snippet; if (_format.isPrint) { snippet = _buildSnippetXml(codeTag, _book.findSnippet(codeTag)); } else { @@ -133,7 +142,7 @@ class CodeTagBlockSyntax extends BlockSyntax { } } -String _buildSnippet(Format format, CodeTag tag, Snippet snippet) { +String _buildSnippet(Format format, CodeTag tag, Snippet? snippet) { // NOTE: If you change this, be sure to update the baked in example snippet // in introduction.md. @@ -155,7 +164,7 @@ String _buildSnippet(Format format, CodeTag tag, Snippet snippet) { if (snippet.addedComma != null) { var commaLine = formatCode( - snippet.file.language, [snippet.addedComma], format, + snippet.file.language, [snippet.addedComma!], format, preClass: "insert-before"); var comma = commaLine.lastIndexOf(","); buffer.write(commaLine.substring(0, comma)); @@ -168,7 +177,7 @@ String _buildSnippet(Format format, CodeTag tag, Snippet snippet) { buffer.writeln('
$lines
'); } - if (snippet.added != null) { + if (snippet.added.isNotEmpty) { var added = formatCode(snippet.file.language, snippet.added, format, preClass: tag.beforeCount > 0 || tag.afterCount > 0 ? "insert" : null); buffer.write(added); @@ -189,7 +198,12 @@ String _buildSnippet(Format format, CodeTag tag, Snippet snippet) { return buffer.toString(); } -String _buildSnippetXml(CodeTag tag, Snippet snippet) { +String _buildSnippetXml(CodeTag tag, Snippet? snippet) { + if (snippet == null) { + print("Undefined snippet ${tag.name}"); + return "ERROR: Missing snippet ${tag.name}\n"; + } + var buffer = StringBuffer(); if (tag.showLocation) buffer.writeln(snippet.locationXml); @@ -209,7 +223,7 @@ String _buildSnippetXml(CodeTag tag, Snippet snippet) { // buffer.write(commaLine.substring(comma + 1)); } - if (snippet.added != null) { + if (snippet.added.isNotEmpty) { // Use different tags based on whether there is context before, after, // neither, or both. String insertTag; @@ -252,7 +266,7 @@ String _buildSnippetXml(CodeTag tag, Snippet snippet) { } void _writeContextHtml(Format format, StringBuffer buffer, List lines, - {String cssClass}) { + {String? cssClass}) { buffer.write(""); @@ -261,7 +275,7 @@ void _writeContextHtml(Format format, StringBuffer buffer, List lines, // https://html.spec.whatwg.org/#element-restrictions // Some snippets deliberately start with a newline which needs to be // preserved, so output an extra (discarded) newline in that case. - if (format.isWeb && lines.first.isEmpty) buffer.writeln(); + if (format.isWeb && lines.isNotEmpty && lines.first.isEmpty) buffer.writeln(); for (var line in lines) { buffer.writeln(line.escapeHtml); diff --git a/tool/lib/src/markdown/html_renderer.dart b/tool/lib/src/markdown/html_renderer.dart index eb1a3dde4..702d82cc7 100644 --- a/tool/lib/src/markdown/html_renderer.dart +++ b/tool/lib/src/markdown/html_renderer.dart @@ -19,10 +19,10 @@ class HtmlRenderer implements NodeVisitor { "ul", }; - StringBuffer buffer; + late StringBuffer buffer; final _elementStack = []; - String _lastVisitedTag; + String? _lastVisitedTag; String render(List nodes) { buffer = StringBuffer(); @@ -85,7 +85,7 @@ class HtmlRenderer implements NodeVisitor { assert(identical(_elementStack.last, element)); if (element.children != null && - element.children.isNotEmpty && + element.children!.isNotEmpty && _blockTags.contains(_lastVisitedTag) && _blockTags.contains(element.tag)) { buffer.writeln(); diff --git a/tool/lib/src/markdown/xml_renderer.dart b/tool/lib/src/markdown/xml_renderer.dart index 351a4b33f..f4489937f 100644 --- a/tool/lib/src/markdown/xml_renderer.dart +++ b/tool/lib/src/markdown/xml_renderer.dart @@ -37,8 +37,8 @@ class XmlRenderer implements NodeVisitor { var buffer = StringBuffer(); buffer.writeln(""); - _Paragraph previousMain; - _Paragraph previousAside; + _Paragraph? previousMain; + _Paragraph? previousAside; for (var paragraph in _paragraphs) { String text; @@ -57,7 +57,7 @@ class XmlRenderer implements NodeVisitor { buffer.write(text); // Only add the paragraph to the tag file buffer if it has a unique tag. - var tags = _tagPattern.allMatches(text).map((match) => match[1]).toSet(); + var tags = _tagPattern.allMatches(text).map((match) => match[1] ?? (throw ArgumentError('Tag missing in match'))).toSet(); if (tags.difference(allTags).isNotEmpty) { tagFileBuffer.write(text); allTags.addAll(tags); @@ -118,7 +118,9 @@ class XmlRenderer implements NodeVisitor { // Convert image tags to just their paths. if (text.startsWith(" parent != null && parent.has(name); + bool isIn(String name) => parent?.has(name) ?? false; /// How many levels of list nesting this context contains. int get listDepth { var depth = 0; - for (var context = this; context != null; context = context.parent) { + for (var context = this as _Context?; + context != null; + context = context.parent) { if (context.name == "ordered" || context.name == "unordered") { depth++; } else if (context.name == "aside") { @@ -401,8 +410,6 @@ class _Context { if (depth > 2) print("Unexpected deep list nesting $this."); switch (tag) { - case "main": - return "p"; case "main": return "p"; case "challenges": @@ -419,7 +426,7 @@ class _Context { case "first": case "item": - tag = "${parent.name}-$tag"; + tag = "${parent!.name}-$tag"; if (depth > 1) tag = "sublist-$tag"; break; @@ -496,7 +503,7 @@ class _Paragraph { return false; } - String prettyPrint(_Paragraph previous) { + String prettyPrint(_Paragraph? previous) { var buffer = StringBuffer(); var tag = context.paragraphTag; @@ -519,7 +526,7 @@ class _Paragraph { /// An inline tag or plain text. class _Inline { /// The tag name if this is an inline tag or `null` if it is text. - final String tag; + final String? tag; String text; @@ -533,7 +540,7 @@ class _Inline { return; } - var fullTag = tag; + var fullTag = tag!; var prefix = context.inlinePrefix; if (prefix != "") fullTag = "$prefix-$fullTag"; diff --git a/tool/lib/src/mustache.dart b/tool/lib/src/mustache.dart index 2a8bace6d..71b6af39f 100644 --- a/tool/lib/src/mustache.dart +++ b/tool/lib/src/mustache.dart @@ -15,10 +15,10 @@ class Mustache { final Map _templates = {}; - Mustache([String templateDirectory]) + Mustache([String? templateDirectory]) : _templateDirectory = templateDirectory ?? p.join("asset", "mustache"); - String render(Book book, Page page, String body, {String template}) { + String render(Book book, Page page, String body, {String? template}) { var part = page.part?.title; var up = "Table of Contents"; @@ -30,14 +30,14 @@ class Mustache { var previousPage = book.adjacentPage(page, -1); var nextPage = book.adjacentPage(page, 1); - String nextType; - if (nextPage != null && nextPage.isChapter) { + String? nextType; + if (nextPage?.isChapter ?? false) { nextType = "Chapter"; - } else if (nextPage != null && nextPage.isPart) { + } else if (nextPage?.isPart ?? false) { nextType = "Part"; } - List> chapters; + List>? chapters; if (page.isPart) { chapters = _makeChapterList(page); } diff --git a/tool/lib/src/page.dart b/tool/lib/src/page.dart index 2959ef6b7..37eb1288f 100644 --- a/tool/lib/src/page.dart +++ b/tool/lib/src/page.dart @@ -23,9 +23,9 @@ class Page { final List chapters = []; /// If this page is a chapter page, the part that contains this page. - final Page part; + final Page? part; - PageFile _file; + PageFile? _file; Page(this.title, this.part, this.numberString, this.ordinal); @@ -46,10 +46,10 @@ class Page { /// The code language used for this chapter page or `null` if this isn't one /// of the main chapter pages. - String get language { + String? get language { if (isPart) return null; - if (part.title == "A Tree-Walk Interpreter") return "java"; - if (part.title == "A Bytecode Virtual Machine") return "c"; + if (part?.title == "A Tree-Walk Interpreter") return "java"; + if (part?.title == "A Bytecode Virtual Machine") return "c"; return null; } @@ -75,7 +75,7 @@ class Page { bool get hasChallenges => _ensureFile().hasChallenges; - String get designNote => _ensureFile().designNote; + String? get designNote => _ensureFile().designNote; Iterable get codeTags => _ensureFile().codeTags.values; @@ -103,7 +103,7 @@ class PageFile { final bool hasChallenges; /// The name of the design note in this page, or `null` if there is none. - final String designNote; + final String? designNote; final Map codeTags; @@ -116,7 +116,7 @@ class Header { /// The header depth: 1 is the page title, 2 header, 3 subheader. final int level; final int headerIndex; - final int subheaderIndex; + final int? subheaderIndex; final String name; Header(this.level, this.headerIndex, this.subheaderIndex, this.name); diff --git a/tool/lib/src/page_parser.dart b/tool/lib/src/page_parser.dart index 1755befff..e9727941b 100644 --- a/tool/lib/src/page_parser.dart +++ b/tool/lib/src/page_parser.dart @@ -14,7 +14,7 @@ final _afterPattern = RegExp(r"(\d+) after"); PageFile parsePage(Page page) { var headers = {}; var codeTagsByName = {}; - String designNote; + String? designNote; var hasChallenges = false; var headerIndex = 0; @@ -27,7 +27,7 @@ PageFile parsePage(Page page) { var match = _codePattern.firstMatch(line); if (match != null) { var codeTag = - _createCodeTag(page, codeTagsByName.length, match[1], match[3]); + _createCodeTag(page, codeTagsByName.length, match[1]!, match[3]); codeTagsByName[codeTag.name] = codeTag; continue; } @@ -35,7 +35,7 @@ PageFile parsePage(Page page) { match = _headerPattern.firstMatch(line); if (match != null) { // Keep track of the headers so we can add section navigation for them. - var headerType = match[1]; + var headerType = match[1]!; var level = headerType.length; var name = line.substring(level).trim().pretty; @@ -73,7 +73,7 @@ PageFile parsePage(Page page) { return PageFile(lines, headers, hasChallenges, designNote, codeTagsByName); } -CodeTag _createCodeTag(Page page, int index, String name, String options) { +CodeTag _createCodeTag(Page page, int index, String name, String? options) { // Parse the location annotations after the name, if present. var showLocation = true; var beforeCount = 0; @@ -88,13 +88,13 @@ CodeTag _createCodeTag(Page page, int index, String name, String options) { var match = _beforePattern.firstMatch(option); if (match != null) { - beforeCount = int.parse(match[1]); + beforeCount = int.parse(match[1]!); continue; } match = _afterPattern.firstMatch(option); if (match != null) { - afterCount = int.parse(match[1]); + afterCount = int.parse(match[1]!); continue; } diff --git a/tool/lib/src/snippet.dart b/tool/lib/src/snippet.dart index 0e2d3865d..144f3979d 100644 --- a/tool/lib/src/snippet.dart +++ b/tool/lib/src/snippet.dart @@ -8,18 +8,19 @@ class Snippet { final SourceFile file; final CodeTag tag; - Location _location; + late Location _location; - int _firstLine; - int _lastLine; + late int _firstLine; + late int _lastLine; - Location get precedingLocation => _precedingLocation; - Location _precedingLocation; + Location get precedingLocation => + _precedingLocation ?? Location(null, "file", file.path); + Location? _precedingLocation; /// If the snippet replaces a line with the same line but with a trailing /// comma, this is that line (with the comma). - String get addedComma => _addedComma; - String _addedComma; + String? get addedComma => _addedComma; + String? _addedComma; final List added = []; final List removed = []; @@ -122,9 +123,10 @@ class Snippet { if (!line.isPresent(tag)) continue; checkedLines++; - // Store the most precise preceding location we find. - if (_precedingLocation == null || - line.location.depth > _precedingLocation.depth) { + // Store the most recently modified time of all files that match [globs]. + var precedingLocation = _precedingLocation; + if (precedingLocation == null || + line.location.depth > precedingLocation.depth) { _precedingLocation = line.location; } } diff --git a/tool/lib/src/source_file_parser.dart b/tool/lib/src/source_file_parser.dart index f1801ef85..2b66b0fe3 100644 --- a/tool/lib/src/source_file_parser.dart +++ b/tool/lib/src/source_file_parser.dart @@ -34,10 +34,10 @@ class SourceFileParser { final List _lines; final List<_ParseState> _states = []; - Location _unnamedTypedef; + Location? _unnamedTypedef; - Location _location; - Location _locationBeforeBlock; + late Location _location; + Location? _locationBeforeBlock; SourceFileParser(this._book, String path, String relative) : _file = SourceFile(relative), @@ -135,7 +135,7 @@ class SourceFileParser { if (match != null) { // Hack. Don't get caught by comments or string literals. if (!line.contains("//") && !line.contains('"')) { - var kind = match[3]; + var kind = match[3] ?? (throw ArgumentError('Type kind missing in match')); var name = match[4]; _location = Location(_location, kind, name); } @@ -150,14 +150,14 @@ class SourceFileParser { match = _namedTypedefPattern.firstMatch(line); if (match != null) { - _location = Location(_location, match[1], match[2]); + _location = Location(_location, match[1] ?? (throw ArgumentError('Location name missing in match')), match[2]); return; } match = _unnamedTypedefPattern.firstMatch(line); if (match != null) { // We don't know the name of the typedef yet. - _location = Location(_location, match[1], null); + _location = Location(_location, match[1] ?? (throw ArgumentError('Location name missing in match')), null); _unnamedTypedef = _location; return; } @@ -175,7 +175,7 @@ class SourceFileParser { // Now we know the typedef name. _unnamedTypedef?.name = match[1]; _unnamedTypedef = null; - _location = _location.parent; + _location = _location.parent ?? (throw StateError('Location parent is null')); } // Use "startsWith" to include lines like "} [aside-marker]". @@ -190,17 +190,17 @@ class SourceFileParser { // If we reached a function declaration, not a definition, then it's done // after one line. if (_location.isFunctionDeclaration) { - _location = _location.parent; + _location = _location.parent ?? (throw StateError('Location parent is null')); } // Module variables are only a single line. if (_location.kind == "variable") { - _location = _location.parent; + _location = _location.parent ?? (throw StateError('Location parent is null')); } // Hack. There is a one-line class in Parser.java. if (line.contains("class ParseError")) { - _location = _location.parent; + _location = _location.parent ?? (throw StateError('Location parent is null')); } } @@ -210,10 +210,12 @@ class SourceFileParser { bool _updateState(String line) { var match = _blockPattern.firstMatch(line); if (match != null) { + var startChapterName = match[1] ?? (throw ArgumentError('Start chapter missing in match')); + var endChapterName = match[3] ?? (throw ArgumentError('End chapter missing in match')); _push( - startChapter: _book.findChapter(match[1]), + startChapter: _book.findChapter(startChapterName), startName: match[2], - endChapter: _book.findChapter(match[3]), + endChapter: _book.findChapter(endChapterName), endName: match[4]); _locationBeforeBlock = _location; return true; @@ -227,7 +229,7 @@ class SourceFileParser { } if (line.trim() == "*/" && _currentState.end != null) { - _location = _locationBeforeBlock; + _location = _locationBeforeBlock ?? (throw StateError('_locationBeforeBlock is null')); _pop(); return true; } @@ -257,7 +259,8 @@ class SourceFileParser { match = _beginChapterPattern.firstMatch(line); if (match != null) { - var chapter = _book.findChapter(match[1]); + var chapterName = match[1] ?? (throw ArgumentError('Chapter missing in match')); + var chapter = _book.findChapter(chapterName); var name = match[2]; // if state.start != None: @@ -294,7 +297,10 @@ class SourceFileParser { _ParseState get _currentState => _states.last; void _push( - {Page startChapter, String startName, Page endChapter, String endName}) { + {Page? startChapter, + String? startName, + Page? endChapter, + String? endName}) { startChapter ??= _currentState.start.chapter; CodeTag start; @@ -304,9 +310,9 @@ class SourceFileParser { start = _currentState.start; } - CodeTag end; + CodeTag? end; if (endChapter != null) { - end = endChapter.findCodeTag(endName); + end = endChapter.findCodeTag(endName ?? (throw ArgumentError('endName missing but endChapter present'))); } _states.add(_ParseState(start, end)); @@ -319,7 +325,7 @@ class SourceFileParser { class _ParseState { final CodeTag start; - final CodeTag end; + final CodeTag? end; _ParseState(this.start, [this.end]); diff --git a/tool/lib/src/split_chapter.dart b/tool/lib/src/split_chapter.dart index 1eca06944..f62378a7c 100644 --- a/tool/lib/src/split_chapter.dart +++ b/tool/lib/src/split_chapter.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:glob/glob.dart'; +import 'package:glob/list_local_fs.dart'; import 'package:path/path.dart' as p; import 'package:pool/pool.dart'; @@ -13,7 +14,7 @@ import 'package:tool/src/source_file_parser.dart'; /// descriptors. var _filePool = Pool(200); -Future splitChapter(Book book, Page chapter, [CodeTag tag]) async { +Future splitChapter(Book book, Page chapter, [CodeTag? tag]) async { var futures = >[]; for (var file in Glob("${chapter.language}/**.{c,h,java}").listSync()) { @@ -24,7 +25,7 @@ Future splitChapter(Book book, Page chapter, [CodeTag tag]) async { } Future _splitSourceFile(Book book, Page chapter, String sourcePath, - [CodeTag tag]) async { + [CodeTag? tag]) async { var relative = p.relative(sourcePath, from: chapter.language); // Don't split the generated files. @@ -38,7 +39,7 @@ Future _splitSourceFile(Book book, Page chapter, String sourcePath, // If we're generating the split for an entire chapter, include all its // snippets. - tag ??= book.lastSnippet(chapter).tag; + tag ??= book.lastSnippet(chapter)!.tag; var outputFile = File(p.join("gen", package, relative)); diff --git a/tool/lib/src/syntax/highlighter.dart b/tool/lib/src/syntax/highlighter.dart index 7ec8034c4..358b4f95e 100644 --- a/tool/lib/src/syntax/highlighter.dart +++ b/tool/lib/src/syntax/highlighter.dart @@ -13,7 +13,7 @@ const _maxLineLength = 67; /// /// Wraps the result in a
 tag with the given [preClass].
 String formatCode(String language, List lines, Format format,
-    {String preClass, int indent = 0}) {
+    {String? preClass, int indent = 0}) {
   return Highlighter(language, format)._highlight(lines, preClass, indent);
 }
 
@@ -33,7 +33,7 @@ void checkLineLength(String line) {
 class Highlighter {
   final Format _format;
   final StringBuffer _buffer = StringBuffer();
-  StringScanner scanner;
+  late StringScanner scanner;
   final Language language;
 
   /// Whether we are in a multi-line macro started on a previous line.
@@ -43,7 +43,7 @@ class Highlighter {
       : language = grammar.languages[language] ??
             (throw "Unknown language '$language'.");
 
-  String _highlight(List lines, String preClass, int indent) {
+  String _highlight(List lines, String? preClass, int indent) {
     if (!_format.isPrint) {
       _buffer.write(" words = {};
   final List rules;
 
-  Language({String keywords, String types, List this.rules}) {
-    keywordType(String wordList, String type) {
+  Language({String? keywords, String? types, required List this.rules}) {
+    keywordType(String? wordList, String type) {
       if (wordList == null) return;
       for (var word in wordList.split(" ")) {
         words[word] = type;
diff --git a/tool/lib/src/syntax/rule.dart b/tool/lib/src/syntax/rule.dart
index e7a075c5a..5a3b941a0 100644
--- a/tool/lib/src/syntax/rule.dart
+++ b/tool/lib/src/syntax/rule.dart
@@ -43,13 +43,13 @@ class CaptureRule extends Rule {
   CaptureRule(String pattern, this.tokenTypes) : super._(pattern);
 
   void applyRule(Highlighter highlighter) {
-    var match = highlighter.scanner.lastMatch;
+    var match = highlighter.scanner.lastMatch ?? (throw StateError('Last match missing'));
     for (var i = 0; i < tokenTypes.length; i++) {
       var type = tokenTypes[i];
       if (type.isNotEmpty) {
         highlighter.writeToken(type, match[i + 1]);
       } else {
-        highlighter.writeText(match[i + 1]);
+        highlighter.writeText(match[i + 1] ?? (throw ArgumentError('Match group ${i + 1} missing')));
       }
     }
   }
@@ -91,7 +91,7 @@ class IdentifierRule extends Rule {
   IdentifierRule() : super._(r"[a-zA-Z_][a-zA-Z0-9_]*");
 
   void applyRule(Highlighter highlighter) {
-    var identifier = highlighter.scanner.lastMatch[0];
+    var identifier = (highlighter.scanner.lastMatch ?? (throw StateError('Last match missing')))[0] ?? (throw ArgumentError('First group missing in last match'));
     var type = highlighter.language.words[identifier] ?? "i";
     highlighter.writeToken(type);
   }
diff --git a/tool/lib/src/term.dart b/tool/lib/src/term.dart
index ec31b09a4..33ca20416 100644
--- a/tool/lib/src/term.dart
+++ b/tool/lib/src/term.dart
@@ -27,7 +27,7 @@ void clearLine() {
   }
 }
 
-void writeLine([String line]) {
+void writeLine([String? line]) {
   clearLine();
   if (line != null) stdout.write(line);
 }
diff --git a/tool/pubspec.lock b/tool/pubspec.lock
index 102521d95..359544fe9 100644
--- a/tool/pubspec.lock
+++ b/tool/pubspec.lock
@@ -5,281 +5,305 @@ packages:
     dependency: transitive
     description:
       name: archive
-      url: "https://pub.dartlang.org"
+      sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff
+      url: "https://pub.dev"
     source: hosted
-    version: "2.0.13"
+    version: "4.0.9"
   args:
     dependency: "direct main"
     description:
       name: args
-      url: "https://pub.dartlang.org"
+      sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
+      url: "https://pub.dev"
     source: hosted
-    version: "1.6.0"
+    version: "2.5.0"
   async:
     dependency: transitive
     description:
       name: async
-      url: "https://pub.dartlang.org"
+      sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
+      url: "https://pub.dev"
     source: hosted
-    version: "2.4.1"
+    version: "2.11.0"
   charcode:
     dependency: "direct main"
     description:
       name: charcode
-      url: "https://pub.dartlang.org"
+      sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a
+      url: "https://pub.dev"
     source: hosted
-    version: "1.1.3"
+    version: "1.4.0"
   cli_repl:
     dependency: transitive
     description:
       name: cli_repl
-      url: "https://pub.dartlang.org"
+      sha256: a2ee06d98f211cb960c777519cb3d14e882acd90fe5e078668e3ab4baab0ddd4
+      url: "https://pub.dev"
     source: hosted
-    version: "0.2.0+1"
+    version: "0.2.3"
   collection:
     dependency: transitive
     description:
       name: collection
-      url: "https://pub.dartlang.org"
+      sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
+      url: "https://pub.dev"
     source: hosted
-    version: "1.14.12"
-  convert:
+    version: "1.18.0"
+  ffi:
     dependency: transitive
     description:
-      name: convert
-      url: "https://pub.dartlang.org"
+      name: ffi
+      sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
+      url: "https://pub.dev"
     source: hosted
-    version: "2.1.1"
-  crypto:
+    version: "2.1.0"
+  file:
+    dependency: transitive
+    description:
+      name: file
+      sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
+      url: "https://pub.dev"
+    source: hosted
+    version: "7.0.1"
+  fixnum:
     dependency: transitive
     description:
-      name: crypto
-      url: "https://pub.dartlang.org"
+      name: fixnum
+      sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
+      url: "https://pub.dev"
     source: hosted
-    version: "2.1.5"
+    version: "1.1.1"
   glob:
     dependency: "direct main"
     description:
       name: glob
-      url: "https://pub.dartlang.org"
+      sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
+      url: "https://pub.dev"
     source: hosted
-    version: "1.2.0"
+    version: "2.1.2"
   http:
     dependency: transitive
     description:
       name: http
-      url: "https://pub.dartlang.org"
+      sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
+      url: "https://pub.dev"
     source: hosted
-    version: "0.12.1"
+    version: "1.1.0"
   http_parser:
     dependency: transitive
     description:
       name: http_parser
-      url: "https://pub.dartlang.org"
+      sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
+      url: "https://pub.dev"
     source: hosted
-    version: "3.1.4"
+    version: "4.0.2"
   image:
     dependency: "direct main"
     description:
       name: image
-      url: "https://pub.dartlang.org"
+      sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce
+      url: "https://pub.dev"
     source: hosted
-    version: "2.1.19"
+    version: "4.8.0"
   js:
     dependency: transitive
     description:
       name: js
-      url: "https://pub.dartlang.org"
+      sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
+      url: "https://pub.dev"
     source: hosted
-    version: "0.6.1+1"
+    version: "0.6.7"
   markdown:
     dependency: "direct main"
     description:
       name: markdown
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "2.1.3"
-  matcher:
-    dependency: transitive
-    description:
-      name: matcher
-      url: "https://pub.dartlang.org"
+      sha256: "1b134d9f8ff2da15cb298efe6cd8b7d2a78958c1b00384ebcbdf13fe340a6c90"
+      url: "https://pub.dev"
     source: hosted
-    version: "0.12.6"
+    version: "7.2.1"
   meta:
     dependency: transitive
     description:
       name: meta
-      url: "https://pub.dartlang.org"
+      sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
+      url: "https://pub.dev"
     source: hosted
-    version: "1.1.8"
+    version: "1.16.0"
   mime_type:
     dependency: "direct main"
     description:
       name: mime_type
-      url: "https://pub.dartlang.org"
+      sha256: d652b613e84dac1af28030a9fba82c0999be05b98163f9e18a0849c6e63838bb
+      url: "https://pub.dev"
     source: hosted
-    version: "0.3.0"
+    version: "1.0.1"
   mustache_template:
     dependency: "direct main"
     description:
       name: mustache_template
-      url: "https://pub.dartlang.org"
+      sha256: a46e26f91445bfb0b60519be280555b06792460b27b19e2b19ad5b9740df5d1c
+      url: "https://pub.dev"
     source: hosted
-    version: "1.0.0+1"
+    version: "2.0.0"
   node_interop:
     dependency: transitive
     description:
       name: node_interop
-      url: "https://pub.dartlang.org"
+      sha256: "4848ac408c0cdd0f70136b755df816a8e4c96c244e5377a3fb3b8f8950666150"
+      url: "https://pub.dev"
     source: hosted
-    version: "1.1.1"
-  node_io:
-    dependency: transitive
-    description:
-      name: node_io
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "1.1.1"
+    version: "2.2.0"
   package_config:
     dependency: transitive
     description:
       name: package_config
-      url: "https://pub.dartlang.org"
+      sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
+      url: "https://pub.dev"
     source: hosted
-    version: "1.9.3"
-  package_resolver:
-    dependency: transitive
-    description:
-      name: package_resolver
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "1.0.10"
+    version: "2.1.0"
   path:
     dependency: "direct main"
     description:
       name: path
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "1.7.0"
-  pedantic:
-    dependency: transitive
-    description:
-      name: pedantic
-      url: "https://pub.dartlang.org"
+      sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
+      url: "https://pub.dev"
     source: hosted
     version: "1.9.0"
   petitparser:
     dependency: transitive
     description:
       name: petitparser
-      url: "https://pub.dartlang.org"
+      sha256: eeb2d1428ee7f4170e2bd498827296a18d4e7fc462b71727d111c0ac7707cfa6
+      url: "https://pub.dev"
     source: hosted
-    version: "3.0.4"
+    version: "6.0.1"
   pool:
     dependency: "direct main"
     description:
       name: pool
-      url: "https://pub.dartlang.org"
+      sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
+      url: "https://pub.dev"
     source: hosted
-    version: "1.4.0"
-  quiver:
+    version: "1.5.1"
+  posix:
     dependency: transitive
     description:
-      name: quiver
-      url: "https://pub.dartlang.org"
+      name: posix
+      sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07"
+      url: "https://pub.dev"
     source: hosted
-    version: "2.1.3"
+    version: "6.5.0"
+  protobuf:
+    dependency: transitive
+    description:
+      name: protobuf
+      sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.1.0"
+  pub_semver:
+    dependency: transitive
+    description:
+      name: pub_semver
+      sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.4"
   sass:
     dependency: "direct main"
     description:
       name: sass
-      url: "https://pub.dartlang.org"
+      sha256: "341b33ebcb8fe3ed6c793bcfe1d01edd886bc58d68ff3febf29c0b391ac24296"
+      url: "https://pub.dev"
     source: hosted
-    version: "1.26.5"
+    version: "1.66.0"
   shelf:
     dependency: "direct main"
     description:
       name: shelf
-      url: "https://pub.dartlang.org"
+      sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
+      url: "https://pub.dev"
     source: hosted
-    version: "0.7.5"
+    version: "1.4.1"
   source_maps:
     dependency: transitive
     description:
       name: source_maps
-      url: "https://pub.dartlang.org"
+      sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703"
+      url: "https://pub.dev"
     source: hosted
-    version: "0.10.9"
+    version: "0.10.12"
   source_span:
     dependency: transitive
     description:
       name: source_span
-      url: "https://pub.dartlang.org"
+      sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
+      url: "https://pub.dev"
     source: hosted
-    version: "1.7.0"
+    version: "1.10.2"
   stack_trace:
     dependency: transitive
     description:
       name: stack_trace
-      url: "https://pub.dartlang.org"
+      sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
+      url: "https://pub.dev"
     source: hosted
-    version: "1.9.3"
+    version: "1.11.1"
   stream_channel:
     dependency: transitive
     description:
       name: stream_channel
-      url: "https://pub.dartlang.org"
+      sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
+      url: "https://pub.dev"
     source: hosted
-    version: "2.0.0"
+    version: "2.1.2"
   stream_transform:
     dependency: transitive
     description:
       name: stream_transform
-      url: "https://pub.dartlang.org"
+      sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
+      url: "https://pub.dev"
     source: hosted
-    version: "1.2.0"
+    version: "2.1.1"
   string_scanner:
     dependency: "direct main"
     description:
       name: string_scanner
-      url: "https://pub.dartlang.org"
+      sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
+      url: "https://pub.dev"
     source: hosted
-    version: "1.0.5"
+    version: "1.4.1"
   term_glyph:
     dependency: transitive
     description:
       name: term_glyph
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "1.1.0"
-  tuple:
-    dependency: transitive
-    description:
-      name: tuple
-      url: "https://pub.dartlang.org"
+      sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
+      url: "https://pub.dev"
     source: hosted
-    version: "1.0.3"
+    version: "1.2.2"
   typed_data:
     dependency: transitive
     description:
       name: typed_data
-      url: "https://pub.dartlang.org"
+      sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
+      url: "https://pub.dev"
     source: hosted
-    version: "1.1.6"
+    version: "1.3.2"
   watcher:
     dependency: transitive
     description:
       name: watcher
-      url: "https://pub.dartlang.org"
+      sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a"
+      url: "https://pub.dev"
     source: hosted
-    version: "0.9.7+15"
+    version: "1.1.4"
   xml:
     dependency: transitive
     description:
       name: xml
-      url: "https://pub.dartlang.org"
+      sha256: af5e77e9b83f2f4adc5d3f0a4ece1c7f45a2467b695c2540381bac793e34e556
+      url: "https://pub.dev"
     source: hosted
-    version: "4.5.1"
+    version: "6.4.2"
 sdks:
-  dart: ">2.11.0 <3.0.0"
+  dart: ">=3.1.0 <4.0.0"
diff --git a/tool/pubspec.yaml b/tool/pubspec.yaml
index 634f3e7f0..a4fbfc9ac 100644
--- a/tool/pubspec.yaml
+++ b/tool/pubspec.yaml
@@ -1,17 +1,17 @@
 name: tool
 publish_to: none
 environment:
-  sdk: '>2.11.0 <3.0.0'
+  sdk: '>=3.0.0 <4.0.0'
 dependencies:
-  args: ^1.6.0
+  args: ^2.5.0
   charcode: ^1.1.3
-  glob: ^1.2.0
-  image: ^2.1.19
-  markdown: ^2.1.3
-  mime_type: ^0.3.0
-  mustache_template: ^1.0.0
+  glob: ^2.1.2
+  image: ^4.8.0
+  markdown: ^7.2.1
+  mime_type: ^1.0.1
+  mustache_template: ^2.0.0
   path: ^1.7.0
   pool: ^1.4.0
   sass: ^1.26.5
-  shelf: ^0.7.5
+  shelf: ^1.4.1
   string_scanner: ^1.0.5
diff --git a/util/c.make b/util/c.make
index 4a49d7b5c..fe4f9138c 100644
--- a/util/c.make
+++ b/util/c.make
@@ -24,7 +24,7 @@ endif
 
 # Mode configuration.
 ifeq ($(MODE),debug)
-	CFLAGS += -O0 -DDEBUG -g
+	CFLAGS += -O0 -DDEBUG -g -U_FORTIFY_SOURCE
 	BUILD_DIR := build/debug
 else
 	CFLAGS += -O3 -flto