Repository: fonttools/fonttools (Python)

Issue:
Demote "feature has not been defined" to a warning
Consider this feature code:

```
feature liga {
    # I'm definitely defining a feature here, I just haven't put any rules in it.
    lookupflag 0;
} liga;

feature aalt {
    feature liga;
} aalt;
```

This currently refuses to compile, complaining that the `liga` feature has not been defined. I think this is wrong on its face, but it's also annoying as well. When generating automatic feature code, or automatically refactoring feature code, you may not want to go back and check whether a feature has any substantive rules and then, if not, remove all references to it; you just want to dump out all the features with the code that it's in them.

Any opinions on demoting the current error to a warning? I'm always in favour of "being permissive in what you accept".


Discussion:
ok but what do you expect the compiler to do in this case? Skip the feature or add an empty feature record?
ah this has to do with building `aalt` only:

https://github.com/fonttools/fonttools/blob/86291b6ef62ad4bdb48495a4b915a597a9652dcf/Lib/fontTools/feaLib/builder.py#L298

yes, it's fine to warn and skip in this case
Empty feature blocks get skipped, right? I guess because we wouldn’t know what table to put them in.
Right, which is why they're being considered "not defined".



Patch:
diff --git a/Lib/fontTools/feaLib/builder.py b/Lib/fontTools/feaLib/builder.py
index 71b1b209ad..867d059e6a 100644
--- a/Lib/fontTools/feaLib/builder.py
+++ b/Lib/fontTools/feaLib/builder.py
@@ -90,7 +90,6 @@ def addOpenTypeFeaturesFromString(
 
 
 class Builder(object):
-
     supportedTables = frozenset(
         Tag(tag)
         for tag in [
@@ -294,9 +293,8 @@ def build_feature_aalt_(self):
             ]
             # "aalt" does not have to specify its own lookups, but it might.
             if not feature and name != "aalt":
-                raise FeatureLibError(
-                    "Feature %s has not been defined" % name, location
-                )
+                warnings.warn("%s: Feature %s has not been defined" % (location, name))
+                continue
             for script, lang, feature, lookups in feature:
                 for lookuplist in lookups:
                     if not isinstance(lookuplist, list):
@@ -1244,7 +1242,7 @@ def set_size_parameters(
     # GSUB 1
     def add_single_subst(self, location, prefix, suffix, mapping, forceChain):
         if self.cur_feature_name_ == "aalt":
-            for (from_glyph, to_glyph) in mapping.items():
+            for from_glyph, to_glyph in mapping.items():
                 alts = self.aalt_alternates_.setdefault(from_glyph, set())
                 alts.add(to_glyph)
             return
@@ -1252,7 +1250,7 @@ def add_single_subst(self, location, prefix, suffix, mapping, forceChain):
             self.add_single_subst_chained_(location, prefix, suffix, mapping)
             return
         lookup = self.get_lookup_(location, SingleSubstBuilder)
-        for (from_glyph, to_glyph) in mapping.items():
+        for from_glyph, to_glyph in mapping.items():
             if from_glyph in lookup.mapping:
                 if to_glyph == lookup.mapping[from_glyph]:
                     log.info(


Test patch:
diff --git a/Tests/feaLib/builder_test.py b/Tests/feaLib/builder_test.py
index 5fbbfaf525..6377db3460 100644
--- a/Tests/feaLib/builder_test.py
+++ b/Tests/feaLib/builder_test.py
@@ -19,6 +19,7 @@
 import tempfile
 import logging
 import unittest
+import warnings
 
 
 def makeTTFont():
@@ -330,12 +331,10 @@ def test_feature_outside_aalt(self):
         )
 
     def test_feature_undefinedReference(self):
-        self.assertRaisesRegex(
-            FeatureLibError,
-            "Feature none has not been defined",
-            self.build,
-            "feature aalt { feature none; } aalt;",
-        )
+        with warnings.catch_warnings(record=True) as w:
+            self.build("feature aalt { feature none; } aalt;")
+            assert len(w) == 1
+            assert "Feature none has not been defined" in str(w[0].message)
 
     def test_GlyphClassDef_conflictingClasses(self):
         self.assertRaisesRegex(