1"use strict";(globalThis.webpackChunkwebsite=globalThis.webpackChunkwebsite||[]).push([[98],{31665(e,n,a){a.r(n),a.d(n,{assets:()=>d,contentTitle:()=>c,default:()=>u,frontMatter:()=>r,metadata:()=>s,toc:()=>o});const s=JSON.parse('{"id":"angle/guide","title":"Angle Guide","description":"The following guide will explain Angle from first principles, leading you through from simple queries to more complex ones.","source":"@site/docs/angle/guide.md","sourceDirName":"angle","slug":"/angle/guide","permalink":"/docs/angle/guide","draft":false,"unlisted":false,"editUrl":"https://github.com/facebookincubator/Glean/tree/main/glean/website/docs/angle/guide.md","tags":[],"version":"current","frontMatter":{"id":"guide","title":"Angle Guide","sidebar_label":"Guide"},"sidebar":"someSidebar","previous":{"title":"Introduction","permalink":"/docs/angle/intro"},"next":{"title":"Query Efficiency","permalink":"/docs/angle/efficiency"}}');var t=a(74848),i=a(28453),l=a(14423);const r={id:"guide",title:"Angle Guide",sidebar_label:"Guide"},c=void 0,d={},o=[{value:"Just the facts",id:"just-the-facts",level:2},{value:"Matching nested facts",id:"matching-nested-facts",level:2},{value:"Union types",id:"union-types",level:2},{value:"Maybe",id:"maybe",level:2},{value:"Choice",id:"choice",level:2},{value:"Variables and more complex queries",id:"variables-and-more-complex-queries",level:2},{value:"Statements",id:"statements",level:2},{value:"Dot syntax",id:"dot-syntax",level:2},{value:"Dot syntax for union types",id:"dot-syntax-for-union-types",level:3},{value:"Extracting the key of a fact",id:"extracting-the-key-of-a-fact",level:3},{value:"If-then-else",id:"if-then-else",level:2},{value:"Arrays",id:"arrays",level:2},{value:"Sets",id:"sets",level:2},{value:"String prefix",id:"string-prefix",level:2},{value:"Tuples",id:"tuples",level:2},{value:"Enums and bool",id:"enums-and-bool",level:2},{value:"Negation",id:"negation",level:2}];function h(e){const n={a:"a",admonition:"admonition",code:"code",em:"em",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",ul:"ul",...(0,i.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.p,{children:"The following guide will explain Angle from first principles, leading you through from simple queries to more complex ones."}),"\n",(0,t.jsxs)(n.p,{children:["If you want to try the examples for yourself, or experiment with\nchanges to the example schema, you should first follow the\ninstructions in ",(0,t.jsx)(n.a,{href:"/docs/walkthrough",children:"Walkthrough"})," to get set up."]}),"\n",(0,t.jsx)(l.FbInternalOnly,{children:(0,t.jsxs)(n.p,{children:["There are also ",(0,t.jsx)(n.a,{href:"https://www.internalfb.com/intern/wiki/Glean/Query/Angle/Angle_examples/",children:"examples of using Angle"})," to query real data."]})}),"\n",(0,t.jsx)(n.h2,{id:"just-the-facts",children:"Just the facts"}),"\n",(0,t.jsxs)(n.p,{children:["Data in Glean is described by a ",(0,t.jsx)(n.em,{children:"schema"}),", which we normally put in a file with the extension ",(0,t.jsx)(n.code,{children:"angle"}),". For the purposes of this guide we\u2019ll use the example schema in ",(0,t.jsx)(n.code,{children:"example.angle"}),". Full details about defining schemas can be found in ",(0,t.jsx)(n.a,{href:"/docs/schema/basic",children:"Schemas"}),". The ",(0,t.jsx)(n.code,{children:"example.angle"})," file contains a schema definition like this:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:"schema example.1 {\n\n# definitions go here\n\n}\n"})}),"\n",(0,t.jsxs)(n.p,{children:["This says we\u2019re defining a schema called ",(0,t.jsx)(n.code,{children:"example"}),", with version 1."]}),"\n",(0,t.jsxs)(n.p,{children:["The schema contains definitions for ",(0,t.jsx)(n.em,{children:"predicates"}),". A predicate is the type of ",(0,t.jsx)(n.em,{children:"facts"}),", which are the individual pieces of information that Glean stores. Our example schema models a simplified class hierarchy for an object-oriented language, starting with a predicate for a ",(0,t.jsx)(n.code,{children:"Class"}),":"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:"predicate Class :\n {\n name : string,\n line : nat,\n }\n"})}),"\n",(0,t.jsxs)(n.p,{children:["This says that the facts of ",(0,t.jsx)(n.code,{children:"Class"})," are records with two fields, a ",(0,t.jsx)(n.code,{children:"name"})," field which contains a ",(0,t.jsx)(n.code,{children:"string"}),", and a ",(0,t.jsx)(n.code,{children:"line"})," field which contains a ",(0,t.jsx)(n.code,{children:"nat"})," (\u201cnat\u201d is short for \u201cnatural number\u201d, which is limited to 64 bits in Glean)."]}),"\n",(0,t.jsx)(n.p,{children:"The simplest type of Angle query is one that just selects facts from\nthe database that match a pattern. For our first Angle query, let\u2019s\nfind a class by its name:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:'facts> example.Class { name = "Pet" }\n{ "id": 1024, "key": { "name": "Pet", "line": 10 } }\n\n1 results, 1 facts, 4.61ms, 117632 bytes, 677 compiled bytes\n'})}),"\n",(0,t.jsx)(n.p,{children:"(The last line contains statistics about query performance from Glean; I\u2019ll leave this out in the rest of the examples.)"}),"\n",(0,t.jsx)(n.p,{children:"What\u2019s going on here?"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["The query consists of the ",(0,t.jsx)(n.em,{children:"predicate name"})," ",(0,t.jsx)(n.code,{children:"example.Class"})," followed by a ",(0,t.jsx)(n.em,{children:"pattern"})," ",(0,t.jsx)(n.code,{children:'{ name = "Pet" }'})]}),"\n",(0,t.jsxs)(n.li,{children:["Note that when we refer to a predicate in a query, the name is ",(0,t.jsx)(n.em,{children:"qualified"})," by prefixing the schema name, so it\u2019s ",(0,t.jsx)(n.code,{children:"example.Class"})," rather than just ",(0,t.jsx)(n.code,{children:"Class"}),"."]}),"\n",(0,t.jsxs)(n.li,{children:["The query returns all the facts of ",(0,t.jsx)(n.code,{children:"example.Class"})," that match the pattern"]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["The shell shows results in JSON format. When you\u2019re making Glean\nqueries from code, the results will normally be decoded into native\ndata types that you can manipulate directly in whatever language\nyou\u2019re using; for more details see ",(0,t.jsx)(n.a,{href:"/docs/schema/thrift",children:"Thrift and\nJSON"}),"."]}),"\n",(0,t.jsxs)(n.p,{children:["Note that each fact has a unique ",(0,t.jsx)(n.code,{children:"id"}),". This is how Glean identifies facts in its database. As a user you normally won\u2019t have to worry about fact ",(0,t.jsx)(n.code,{children:"id"}),"s; you can think of them like memory addresses."]}),"\n",(0,t.jsxs)(n.p,{children:["The pattern specifies which facts to return. In the example above, our pattern is matching a record type and specifying a subset of the fields: just the ",(0,t.jsx)(n.code,{children:"name"})," field. We could match the ",(0,t.jsx)(n.code,{children:"line"})," field instead:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=sh",children:'facts> example.Class { line = 20 }\n{ "id": 1025, "key": { "name": "Lizard", "line": 20 } }\n'})}),"\n",(0,t.jsx)(n.admonition,{type:"note",children:(0,t.jsxs)(n.p,{children:["Your patterns should normally match fields at the ",(0,t.jsx)(n.em,{children:"beginning"})," of the\nrecord, because facts in the database are indexed by a prefix of the\nfields. Matching a field in the middle of the record works by scanning\nall the facts, which could be expensive. We\u2019ll get into this in more\ndetail in ",(0,t.jsx)(n.a,{href:"/docs/angle/efficiency",children:"Query Efficiency"}),"."]})}),"\n",(0,t.jsx)(n.p,{children:'What other kinds of patterns can we use? Well, the simplest patterns are the wildcard, \u201c_\u201d, which matches anything, and "never", which always fail
1s to match.'}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:'facts> example.Class _\n{ "id": 1026, "key": { "name": "Fish", "line": 30 } }\n{ "id": 1027, "key": { "name": "Goldfish", "line": 40 } }\n{ "id": 1025, "key": { "name": "Lizard", "line": 20 } }\n{ "id": 1024, "key": { "name": "Pet", "line": 10 } }\nfacts> example.Class never\n(no results)\n'})}),"\n",(0,t.jsxs)(n.p,{children:["We\u2019ll introduce more kinds of pattern in the following sections. The full list of patterns can be found in ",(0,t.jsx)(n.a,{href:"/docs/angle/reference",children:"Angle Reference"}),"."]}),"\n",(0,t.jsx)(n.h2,{id:"matching-nested-facts",children:"Matching nested facts"}),"\n",(0,t.jsx)(n.p,{children:"The real power of Glean comes from relationships between facts. Facts can refer directly to other facts, and we can write queries that directly match on these connections."}),"\n",(0,t.jsx)(n.p,{children:"Our example schema has a predicate that expresses the inheritance relationship between classes:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:"predicate Parent :\n {\n child : Class,\n parent : Class,\n }\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Let\u2019s find what ",(0,t.jsx)(n.code,{children:"Fish"})," inherits from:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:'facts> example.Parent { child = { name = "Fish" }}\n{\n "id": 1029,\n "key": { "child": { "id": 1026, "key": { "name": "Fish", "line": 30 } }, "parent": { "id": 1024, "key": { "name": "Pet", "line": 10 } } }\n}\n'})}),"\n",(0,t.jsx)(n.p,{children:"Let\u2019s break this down."}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:'{ child = { name = "Fish" }}'})," is a pattern that matches the key type of ",(0,t.jsx)(n.code,{children:"Parent"})]}),"\n",(0,t.jsxs)(n.li,{children:["So, looking at the schema, ",(0,t.jsx)(n.code,{children:'{ name = "Fish" }'})," is a pattern that should match the ",(0,t.jsx)(n.code,{children:"Class"})," in the field ",(0,t.jsx)(n.code,{children:"child"}),"."]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["By default Angle queries recursively expand facts in the results. We can see in the above result that the ",(0,t.jsx)(n.code,{children:"child"})," and ",(0,t.jsx)(n.code,{children:"parent"})," fields contain the full facts they point to. If we want the result to be \u201cshallow\u201d, meaning it contains just the facts that match and not the nested facts, we can ask Glean to not expand the content of those references. In the shell this is done by running the command ",(0,t.jsx)(n.code,{children:":ex
1pand off"}),":"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:'facts> :expand off\nfacts> example.Parent { child = { name = "Fish" }}\n{ "id": 1029, "key": { "child": { "id": 1026 }, "parent": { "id": 1024 } } }\n'})}),"\n",(0,t.jsx)(n.p,{children:"We can of course go the other way and find all the children of a class:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:'facts> example.Parent { parent = { name = "Pet" }}\n{\n "id": 1028,\n "key": {\n "child": { "id": 1025, "key": { "name": "Lizard", "line": 20 } },\n "parent": { "id": 1024, "key": { "name": "Pet", "line": 10 } }\n }\n}\n{\n "id": 1029,\n "key": {\n "child": { "id": 1026, "key": { "name": "Fish", "line": 30 } },\n "parent": { "id": 1024, "key": { "name": "Pet", "line": 10 } }\n }\n}\n'})}),"\n",(0,t.jsxs)(n.p,{children:["But as before, note that this would be an inefficient query if we had a lot of data because the pattern is matching on the second field of ",(0,t.jsx)(n.code,{children:"Parent"})," (namely ",(0,t.jsx)(n.code,{children:"parent"}),"). Later we\u2019ll see how to make these queries more efficient using a derived predicate."]}),"\n",(0,t.jsx)(n.h2,{id:"union-types",children:"Union types"}),"\n",(0,t.jsxs)(n.p,{children:["Our examples so far have dealt with record types. Glean also supports ",(0,t.jsx)(n.em,{children:"union types"}),", also called ",(0,t.jsx)(n.em,{children:"sum types"}),", which are used to express multiple alternatives. For example, let\u2019s expand our schema to include class members which can be either a method or a variable:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:"predicate Has :\n {\n class_ : Class,\n has : Member,\n access : enum { public_ | private_ },\n }\n\npredicate Member :\n {\n method : { name : string, doc : maybe str
1ing } |\n variable : { name : string }\n }\n"})}),"\n",(0,t.jsxs)(n.p,{children:["The predicate ",(0,t.jsx)(n.code,{children:"Has"})," maps a ",(0,t.jsx)(n.code,{children:"Class"})," to a ",(0,t.jsx)(n.code,{children:"Member"})," (with a ",(0,t.jsx)(n.code,{children:"public_"})," or ",(0,t.jsx)(n.code,{children:"private_"})," annotation), and a ",(0,t.jsx)(n.code,{children:"Member"})," is either ",(0,t.jsx)(n.code,{children:"method"})," or ",(0,t.jsx)(n.code,{children:"variable"}),", with some associated data. Note that a ",(0,t.jsx)(n.code,{children:"Class"})," might have more than one ",(0,t.jsx)(n.code,{children:"Member"}),", which is fine: there can be multiple ",(0,t.jsx)(n.code,{children:"Has"})," facts for a given ",(0,t.jsx)(n.code,{children:"Class"}),"."]}),"\n",(0,t.jsxs)(n.admonition,{type:"note",children:[(0,t.jsxs)(n.p,{children:["The schema uses ",(0,t.jsx)(n.code,{children:"class_"})," rather than ",(0,t.jsx)(n.code,{children:"class"})," as a field name, because\n",(0,t.jsx)(n.code,{children:"class"})," is a reserved word in Angle. Similarly, we added trailing\nunderscores to ",(0,t.jsx)(n.code,{children:"public_"})," and",(0,t.jsx)(n.code,{children:"private_"})," for the same reason."]}),(0,t.jsx)(n.p,{children:"There are many such reserved words, which are reserved not because Angle uses them, but because they cause problems for code that is automatically generated from the schema. To avoid having too many ad-hoc language-specific naming rules, Glean prevents certain problematic names from being used in the schema. The Angle compiler will tell you if you try to use a reserved word."})]}),"\n",(0,t.jsxs)(n.p,{children:["Let\u2019s find classes that have a variable called ",(0,t.jsx)(n.code,{children:"fins"}),":"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:'facts> example.Has { has = { variable = { name = "fins" }}}\n{\n "id": 1036,\n "key": {\n "class_": { "id": 1026, "key": { "name": "Fish", "line": 30 } },\n "has": { "id": 1035, "key": { "variable": { "name": "fins" } } },\n "access": 1\n }\n}\n'})}),"\n",(0,t.jsxs)(n.p,{children:["The key thing here is that we matched on ",(0,t.jsx)(n.code,{children:"Member"})," which is a union type, using the pattern ",(0,t.jsx)(n.code,{children:'{ variable = { name = "fins" }}'}),". A pattern to match a union type looks very much like a record pattern, but it can have only a single field, in this case either ",(0,t.jsx)(n.code,{children:"variable"})," or ",(0,t.jsx)(n.code,{children:"method"}),"."]}),"\n",(0,t.jsx)(n.h2,{id:"maybe",children:"Maybe"}),"\n",(0,t.jsxs)(n.p,{children:["Glean has one built-in union type called ",(0,t.jsx)(n.code,{children:"maybe"}),", which is useful when we want to have optional values in the data. It's used in our example schema to attach optional documentation to a class member:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:"predicate Member :\n {\n method : { name : string, doc : maybe str
1ing } |\n variable : { name : string }\n }\n"})}),"\n",(0,t.jsxs)(n.p,{children:["The type ",(0,t.jsx)(n.code,{children:"maybe string"})," behaves exactly as if it were defined as the union type ",(0,t.jsx)(n.code,{children:"{ nothing | just : string }"}),". That means we can write a pattern that matches it, exactly as we would write a pattern for ",(0,t.jsx)(n.code,{children:"{ nothing | just : string }"}),":"]}),"\n",(0,t.jsx)(n.p,{children:"Methods without documentation:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:"facts> example.Member { method = { doc = nothing } }\n"})}),"\n",(0,t.jsx)(n.p,{children:"Methods with documentation:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:"facts> example.Member { method = { doc = { just = _ }}}\n"})}),"\n",(0,t.jsx)(n.h2,{id:"choice",children:"Choice"}),"\n",(0,t.jsxs)(n.p,{children:["In a pattern we can express multiple alternatives by separating patterns with a vertical bar ",(0,t.jsx)(n.code,{children:"|"}),"."]}),"\n",(0,t.jsx)(n.p,{children:"For example, we can find classes on lines 20 or 30:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:'facts> example.Class { line = 20 | 30 }\n{ "id": 1025, "key": { "name": "Lizard", "line": 20 } }\n{ "id": 1026, "key": { "name": "Fish", "line": 30 } }\n'})}),"\n",(0,t.jsxs)(n.p,{children:["Or we can find all the classes that have either a ",(0,t.jsx)(n.code,{children:"method"})," called ",(0,t.jsx)(n.code,{children:"feed"})," or a ",(0,t.jsx)(n.code,{children:"variable"})," with any name:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:'facts> example.Has { has = { method = { name = "feed" }} | { variable = _ }}\n\n(results omitted)\n'})}),"\n",(0,t.jsx)(n.h2,{id:"variables-and-more-complex-queries",children:"Variables and more complex queries"}),"\n",(0,t.jsx)(n.p,{children:"So far we\u2019ve seen how to query for facts by matching patterns, including matching nested facts. In this section we\u2019ll see how to construct more complex queries that combine matching facts from multiple predicates."}),"\n",(0,t.jsxs)(n.p,{children:["Suppose we want to find all the parents of classes that have a variable called ",(0,t.jsx)(n.code,{children:"fins"}),". We need to build a query that will"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["find the classes with a variable called ",(0,t.jsx)(n.code,{children:"fins"})," using ",(0,t.jsx)(n.code,{children:"example.Has"})," as we did above"]}),"\n",(0,t.jsxs)(n.li,{children:["find their parents using ",(0,t.jsx)(n.code,{children:"example.Parent"})]}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"We can combine these two as follows:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:'example.Has\n {\n class_ = C,\n has = { variable = { name = "fins" }}\n };\nexample.Parent { child = C }\n'})}),"\n",(0,t.jsx)(n.admonition,{type:"note",children:(0,t.jsxs)(n.p,{children:["I\u2019ve written this on several lines with indentation to illustrate it\nbetter, to do this in the shell you will need to use the ",(0,t.jsx)(n.code,{children:":edit"}),"\ncommand to put the query in a temporary file."]})}),"\n",(0,t.jsxs)(n.p,{children:["The key thing here is that we used a ",(0,t.jsx)(n.em,{children:"variable"})," ",(0,t.jsx)(n.code,{children:"C"})," to stand for the ",(0,t.jsx)(n.code,{children:"class_"})," field when matching facts of ",(0,t.jsx)(n.code,{children:"example.Has"}),", and then we searched for ",(0,t.jsx)(n.code,{children:"example.Parent"})," facts with the same value of ",(0,t.jsx)(n.code,{children:"C"})," for the ",(0,t.jsx)(n.code,{children:"child"})," field."]}),"\n",(0,t.jsxs)(n.p,{children:["Note that variables must ",(0,t.jsx)(n.em,{children:"always"})," begin with an upper-case letter, while schema names (",(0,t.jsx)(n.code,{children:"example)"})," and field names (",(0,t.jsx)(n.code,{children:"child"}),") begin with a lower-case letter."]}),"\n",(0,t.jsxs)(n.p,{children:["The semicolon separates multiple ",(0,t.jsx)(n.em,{children:"statements"})," in a query. When there are multiple statements the results of the query are the facts that match the last statement, in this case the ",(0,t.jsx)(n.code,{children:"example.Parent"}),". Let\u2019s try it:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:'facts> example.Has { class_ = C, has = { variable = { name = "fins" }}}; example.Parent { child = C }\n{\n "id": 1029,\n "key": {\n "child": { "id": 1026, "key": { "name": "Fish", "line": 30 } },\n "parent": { "id": 1024, "key": { "name": "Pet", "line": 10 } }\n }\n}\n'})}),"\n",(0,t.jsx)(n.p,{children:"Suppose we don\u2019t care too much about the child here, we only care about getting a list of the parents. We can avoid returning the redundant information by specifying explicitly what it is
1we want to return from the query:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:'P where\n example.Has\n {\n class_ = C,\n has = { variable = { name = "fins" }}\n };\n example.Parent { child = C, parent = P }\n'})}),"\n",(0,t.jsxs)(n.p,{children:["The general form of the query is ",(0,t.jsx)(n.em,{children:(0,t.jsx)(n.code,{children:"expression"})})," ",(0,t.jsx)(n.code,{children:"where"})," ",(0,t.jsx)(n.em,{children:(0,t.jsx)(n.code,{children:"statements"})}),", where ",(0,t.jsx)(n.em,{children:(0,t.jsx)(n.code,{children:"expression"})})," is an arbitrary expression and each statement is a pattern that matches some facts. The results of the query are the distinct values of ",(0,t.jsx)(n.em,{children:(0,t.jsx)(n.code,{children:"expression"})})," for which all the statements match facts in the database."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:'facts> P where example.Has { class_ = C, has = { variable = { name = "fins" }}}; example.Parent { child = C, parent = P }\n{ "id": 1024, "key": { "name": "Pet", "line": 10 } }\n'})}),"\n",(0,t.jsx)(n.h2,{id:"statements",children:"Statements"}),"\n",(0,t.jsxs)(n.p,{children:["In general, a statement can be of the form ",(0,t.jsx)(n.em,{children:"A = B."})," For example, if we write"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:'C = example.Class { name = "Fish" };\nexample.Parent { child = C }\n'})}),"\n",(0,t.jsx)(n.p,{children:"that\u2019s the same as"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:'example.Parent { child = { name = "Fish" }}\n'})}),"\n",(0,t.jsx)(n.p,{children:"A statement can have a pattern on either side, for example"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:'C where\n C = example.Class { name = N };\n N = "Fish" | "Goldfish"\n'})}),"\n",(0,t.jsxs)(n.p,{children:["A statement can itself be a set of alternatives separated by a vertical bar ",(0,t.jsx)(n.code,{children:"|"}),". For example, we can find classes that are either a parent of the ",(0,t.jsx)(n.code,{children:"Goldfish"})," or have a ",(0,t.jsx)(n.code,{children:"feed"})," method:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:'C where\n example.Parent { child = { name = "Goldfish" }, parent = C } |\n example.Has { class_ = C, has = { method = { name = "feed" }}}\n'})}),"\n",(0,t.jsx)(n.h2,{id:"dot-syntax",children:"Dot syntax"}),"\n",(0,t.jsx)(n.p,{children:'So far we\'ve been extracting fields from records by writing patterns,\nbut we can also extract fields from records using the traditional "dot\nsyntax". For example, instead of'}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:'example.Parent { child = { name = "Fish" }}\n'})}),"\n",(0,t.jsx)(n.p,{children:"we could write"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:'example.Parent P where P.child.name = "Fish"\n'})}),"\n",(0,t.jsx)(n.p,{children:"Here"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"example.Parent P"})," selects facts of ",(0,t.jsx)(n.code,{children:"example.Parent"})," and binds the key to the variable ",(0,t.jsx)(n.code,{children:"P"})]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:'P.child.name = "Fish"'})," is a constraint on the ",(0,t.jsx)(n.code,{children:"name"})," field of the ",(0,t.jsx)(n.code,{children:"child"})," field of ",(0,t.jsx)(n.code,{children:"P"})]}),"\n",(0,t.jsxs)(n.li,{children:["the query returns all facts of ",(0,t.jsx)(n.code,{children:"example.Parent"})," that satisfy the constraint"]}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"Dot syntax tends to be more concise than patterns when there are\ndeeply-nested records, because it avoids all the nested braces."}),"\n",(0,t.jsx)(n.h3,{id:"dot-syntax-for-union-types",children:"Dot syntax for union types"}),"\n",(0,t.jsx)(n.p,{children:"Matching union types can also be achieved using dot syntax. For\nexample, earlier we had"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:'example.Has { has = { variable = { name = "fins" }}}\n'})}),"\n",(0,t.jsx)(n.p,{children:"using dot syntax this would be"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:'example.Has H where H.has.variable?.name = "fins"\n'})}),"\n",(0,t.jsxs)(n.p,{children:["Note that when selecting a union type we add a '?' suffix,\nas with ",(0,t.jsx)(n.code,{children:".variable?"})," in the example above. This makes it more obvious\nthat we're doing something conditional: if ",(0,t.jsx)(n.code,{children:"X.has"})," is not a\n",(0,t.jsx)(n.code,{children:"variable"}),", then ",(0,t.jsx)(n.code,{children:"X.has.variable?"})," has no values."]}),"\n",(0,t.jsxs)(n.p,{children:["Selecting from union types works nicely with choice (",(0,t.jsx)(n.code,{children:"|"}),"):"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:"Name where\n example.Has H;\n Name = (H.has.variable?.name | H.has.method?.name)\n"})}),"\n",(0,t.jsx)(n.p,{children:"returns all the names of variables and methods."}),"\n",(0,t.jsx)(n.h3,{id:"extracting-the-key-of-a-fact",children:"Extracting the key of a fact"}),"\n",(0,t.jsxs)(n.p,{children:["It's sometimes useful to be able to extract the key of a fact. If we\nhave a variable ",(0,t.jsx)(n.code,{children:"X"})," of some predicate type, then we can extract the\nkey of ",(0,t.jsx)(n.code,{children:"X"})," with ",(0,t.jsx)(n.code,{children:"X.*"}),". If ",(0,t.jsx)(n.code,{children:"X"})," has type ",(0,t.jsx)(n.code,{children:"P"})," and ",(0,t.jsx)(n.code,{children:"P"})," is a predicate with\nkey type ",(0,t.jsx)(n.code,{children:"K"}),", then ",(0,t.jsx)(n.code,{children:"X.*"})," has type ",(0,t.jsx)(n.code,{children:"K"}),"."]}),"\n",(0,t.jsxs)(n.p,{children:["Usually we don't need to extract the key explicitly because Angle does\nit automatically. For example if ",(0,t.jsx)(n.code,{children:"X : example.Class"}),", then ",(0,t.jsx)(n.code,{children:"X.name"})," is\nshorthand for ",(0,t.jsx)(n.code,{children:"X.*.name"}),". But sometimes we just want the key without\nselecting a field, or perhaps the key isn't a record. In those cases,\n",(0,t.jsx)(n.code,{children:"X.*"})," can be useful."]}),"\n",(0,t.jsx)(n.h2,{id:"if-then-else",children:"If-then-else"}),"\n",(0,t.jsxs)(n.p,{children:["We can conditionally match patterns using ",(0,t.jsx)(n.code,{children:"if then else"}),"."]}),"\n",(0,t.jsxs)(n.p,{children:["Variables matched in the condition will be available in the ",(0,t.jsx)(n.code,{children:"then"})," branch."]}),"\n",(0,t.jsxs)(n.p,{children:["Whilst a choice will always evaluate both of its branches, the ",(0,t.jsx)(n.code,{children:"else"})," branch of an if will\nnever be evaluated if the condition succeeds at least once."]}),"\n",(0,t.jsx)(n.p,{children:"For example, we could get all child classes if inheritance is being used in the codebase, or\nretrieve all classes if it isn't."}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:'facts > if (example.Parent { child = X }) then X else example.Class _\n { "id": 1025, "key": { "name": "Lizard", "line": 20 } }\n { "id": 1026, "key": { "name": "Fish", "line": 30 } }\n { "id": 1027, "key": { "name": "Goldfish", "line": 40 } }\n'})}),"\n",(0,t.jsxs)(n.p,{children:["Please note that ",(0,t.jsx)(n.code,{children:"if"})," cannot be used in stored derived predicates. This\nis the case because they require the use of negation, which is disallowed in\nstored predicates."]}),"\n",(0,t.jsx)(n.h2,{id:"arrays",children:"Arrays"}),"\n",(0,t.jsxs)(n.p,{children:["When the schema uses an array, we need to be able to write queries that traverse the elements of the array. For example, a common use of an array is to represent the list of declarations in a source file. Our example schema defines the ",(0,t.jsx)(n.code,{children:"FileClasses"})," predicate:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:"predicate FileClasses :\n {\n file : string,\n classes : [Class]\n }\n"})}),"\n",(0,t.jsxs)(n.p,{children:["The goal here is to map efficie
1ntly from a filename to the list of classes defined in that file. Suppose we want to write a query that finds all the classes called ",(0,t.jsx)(n.code,{children:"Goldfish"})," in the file ",(0,t.jsx)(n.code,{children:"petshop.example"}),", we could do it like this:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:'example.FileClasses { file = "petshop.example", classes = Cs };\n{ name = "Goldfish" } = Cs[..]\n'})}),"\n",(0,t.jsxs)(n.p,{children:["The second line is the interesting one: ",(0,t.jsx)(n.code,{children:'{ name = "Goldfish" } = Cs[..]'})," means"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["on the right-hand side, ",(0,t.jsx)(n.code,{children:"Cs[..]"})," means \u201ceach element of the array ",(0,t.jsx)(n.code,{children:"Cs"}),"\u201d"]}),"\n",(0,t.jsxs)(n.li,{children:["the left-hand side is a pattern, filtering only those ",(0,t.jsx)(n.code,{children:"Class"})," facts that match ",(0,t.jsx)(n.code,{children:'{ name = "Goldfish" }'})]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["We can also match the whole array with a pattern of the form ",(0,t.jsx)(n.code,{children:"[ p1, p2, .. ]"})]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:'facts> X where [_,X,_] = [1,2,3]\n{ "id": 1040, "key": 2 }\n'})}),"\n",(0,t.jsx)(n.p,{children:"Or if we don't care about the length of the array:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:'facts> X where [_,X, ..] = [1,2,3]\n{ "id": 1040, "key": 2 }\n'})}),"\n",(0,t.jsx)(n.h2,{id:"sets",children:"Sets"}),"\n",(0,t.jsx)(n.p,{children:"Sets are similar to arrays but helpful when the order of the elements are not important and duplicates are also irrelevant.\nA common example is when storing cross references. For instance, the python schema has a predicate which contains all\nname cross references in a file. The cross references are currently stored in an array but it could be stored in a set as below."}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:"predicate XRefsViaNameByFile:\n {\n file: src.File,\n xrefs: set XRefViaName,\n }\n"})}),"\n",(0,t.jsx)(n.p,{children:"If we want to know for a particular file and a particular name, where it is used we could write the following query:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:'XRefsViaNameByFile { file = "foo.py", xrefs = XRefs };\n{ target = { name = "Bar" } } = XRefs[..]\n'})}),"\n",(0,t.jsxs)(n.p,{children:["The second line uses the ",(0,t.jsx)(n.code,{children:"[..]"})," syntax which works the same as for arrays, each element of the set ",(0,t.jsx)(n.code,{children:"XRefs"})," is produced as a different result."]}),"\n",(0,t.jsxs)(n.p,{children:["We can also create new sets from the results of a query. This is done using the ",(0,t.jsx)(n.code,{children:"all"})," construct. For instance\n",(0,t.jsx)(n.code,{children:"all (1 | 2 | 3)"})," is a set containing the number ",(0,t.jsx)(n.code,{children:"1"}),", ",(0,t.jsx)(n.code,{children:"2"}),", and ",(0,t.jsx)(n.code,{children:"3"}),"."]}),"\n",(0,t.jsxs)(n.p,{children:["The ",(0,t.jsx)(n.code,{children:"all"})," construct can be used in combination with the ",(0,t.jsx)(n.code,{children:"[..]"})," construct to, for instance, map over a set\nof elements and transform them. In the example below, the second line takes each element of the ",(0,t.jsx)(n.code,{children:"StringSet"})," and\napplies the primitive ",(0,t.jsx)(n.code,{children:"prim.toLower"})," to it. The result is a set where all the strings are lowercase."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:'StringSet = all ("Foo" | "Bar" | "Baz" );\nall (String = StringSet[..]; prim.toLower String)\n'})}),"\n",(0,t.jsx)(n.h2,{id:"string-prefix",children:"String prefix"}),"\n",(0,t.jsxs)(n.p,{children:["We\u2019ve seen many examples of patterns that match strings. Glean also supports matching strings by ",(0,t.jsx)(n.em,{children:"prefix"}),"; for example:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:'facts> example.Class { name = "F".. }\n{ "id": 1026, "key": { "name": "Fish", "line": 30 } }\n'})}),"\n",(0,t.jsxs)(n.p,{children:["The syntax ",(0,t.jsx)(n.code,{children:'"F"..'})," means ",(0,t.jsx)(n.em,{children:"strings beginning with the prefix"})," ",(0,t.jsx)(n.code,{children:'\u201dF"'}),"."]}),"\n",(0,t.jsx)(n.admonition,{type:"note",children:(0,t.jsx)(n.p,{children:"Why only prefix and not substring matching in general? Prefix matching can be supported efficie
1ntly by Glean\u2019s prefix-tree representation of the fact database. Other kinds of string matching could be supported, but they wouldn\u2019t be able to exploit the database representation so there\u2019s little advantage to implementing them in Angle compared with filtering on the client-side."})}),"\n",(0,t.jsx)(n.h2,{id:"tuples",children:"Tuples"}),"\n",(0,t.jsxs)(n.p,{children:["A ",(0,t.jsx)(n.em,{children:"tuple"})," is just a a way of writing a record without the field names. So for example, instead of"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:"example.Parent { child = C }\n"})}),"\n",(0,t.jsx)(n.p,{children:"we could write"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:"example.Parent { C, _ }\n"})}),"\n",(0,t.jsxs)(n.p,{children:["When using a tuple you have to list ",(0,t.jsx)(n.em,{children:"all"})," the fields, in the same order as they are declared in the schema. That's why ",(0,t.jsx)(n.code,{children:"{ child = C }"})," becomes ",(0,t.jsx)(n.code,{children:"{ C, _ }"})," when written as a tuple."]}),"\n",(0,t.jsx)(n.p,{children:"There are upsides and downsides to using the tuple notation:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"Pro: more concise"}),"\n",(0,t.jsx)(n.li,{children:"Con: brittle and sensitive to changes in the schema. If we add a field, then tuple patterns will break whereas record patterns won't."}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:['As a rule of thumb we tend to use tuple syntax in cases where the predicate is "obviously" a relation, such as ',(0,t.jsx)(n.code,{children:"example.Parent"}),", but we wouldn't use tuple syntax for more complex records."]}),"\n",(0,t.jsx)(n.h2,{id:"enums-and-bool",children:"Enums and bool"}),"\n",(0,t.jsxs)(n.p,{children:["An ",(0,t.jsx)(n.code,{children:"enum"})," type is a set of named constants. In the ",(0,t.jsx)(n.code,{children:"Has"})," predicate we used an ",(0,t.jsx)(n.code,{children:"enum"})," type to indicate whether a class member is ",(0,t.jsx)(n.code,{children:"public_"})," or ",(0,t.jsx)(n.code,{children:"private_"}),":"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:"predicate Has :\n {\n class_ : Class,\n has : Member,\n access : enum { public_ | private_ },\n }\n"})}),"\n",(0,t.jsxs)(n.p,{children:["To match an ",(0,t.jsx)(n.code,{children:"enum"})," we just use the appropriate identifier, in this case ",(0,t.jsx)(n.code,{children:"public_"})," or ",(0,t.jsx)(n.code,{children:"private"}),":"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:'facts> example.Has { access = private_ }\n{ "id": 1036, "key": { "class_": { "id": 1026 }, "has": { "id": 1035 }, "access": 1 } }\n'})}),"\n",(0,t.jsxs)(n.p,{children:["Note that in the JSON format results, an ",(0,t.jsx)(n.code,{children:"enum"})," is represented by an integer. When you make queries in code, the ",(0,t.jsx)(n.code,{children:"enum"})," will be represented by an appropriate type, such as a ",(0,t.jsx)(n.code,{children:"data"})," type in Haskell."]}),"\n",(0,t.jsxs)(n.p,{children:["The boolean type ",(0,t.jsx)(n.code,{children:"bool"})," is a special case of an ",(0,t.jsx)(n.code,{children:"enum"}),", defined like this:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:"type bool = enum { false | true }\n"})}),"\n",(0,t.jsx)(n.admonition,{type:"note",children:(0,t.jsxs)(n.p,{children:["Normally the constants of an ",(0,t.jsx)(n.code,{children:"enum"})," should begin with a lower case\nletter. You can use constants beginning with an upper-case letter, but\nto distinguish the constant from a variable name you may sometimes\nneed to provide a type signature, e.g. ",(0,t.jsx)(n.code,{children:"Constant : Type"})," rather than\njust ",(0,t.jsx)(n.code,{children:"Constant"}),"."]})}),"\n",(0,t.jsx)(n.h2,{id:"negation",children:"Negation"}),"\n",(0,t.jsxs)(n.p,{children:["If we want results that do not match a certain criterion, we can use ",(0,t.jsx)(n.code,{children:"!"})," to\nspecify a subquery that should fail. A subquery fails if it doesn't return any\nresult."]}),"\n",(0,t.jsx)(n.p,{children:"For example, we can find classes that don't have methods"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:'facts> C where C = example.Class _; !(example.Has { class_ = C, has = { method = _ } })\n{ "id": 1026, "key": { "name": "Fish", "line": 30 } }\n{ "id": 1027, "key": { "name": "Goldfish", "line": 40 } }\n{ "id": 1025, "key": { "name": "Lizard", "line": 20 } }\n'})}),"\n",(0,t.jsx)(n.p,{children:"Or we could find the maximum element in an array"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-lang=angle",children:'facts> X where Values = [5,1,2,3]; X = Values[..]; !(Y = Values[..]; Y > X)\n{ "id": 1091, "key": 5 }\n'})}),"\n",(0,t.jsxs)(n.p,{children:["The query asks for the ",(0,t.jsx)(n.code,{children:"X"})," for which given all values of ",(0,t.jsx)(n.code,{children:"Y"})," ",(0,t.jsx)(n.em,{children:"none"})," is greater\nthan it. If ",(0,t.jsx)(n.code,{children:"Y = Values[..]"})," were outside of the negation, the meaning would\nbe give me all ",(0,t.jsx)(n.code,{children:"X"})," for which there is ",(0,t.jsx)(n.em,{children:"at least one"})," ",(0,t.jsx)(n.code,{children:"Y"})," that is not greater\nthan it. The answer to that would be all elements."]})]})}function u(e={}){const{wrapper:n}={...(0,i.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(h,{...e})}):h(e)}},80510(e,n,a){var s=this&&this.__awaiter||function(e,n,a,s){return new(a||(a=Promise))(function(t,i){function l(e){try{c(s.next(e))}catch(n){i(n)}}function r(e){try{c(s.throw(e))}catch(n){i(n)}}function c(e){var n;e.done?t(e.value):(n=e.value,n instanceof a?n:new a(function(e){e(n)})).then(l,r)}c((s=s.apply(e,n||[])).next())})};Object.defineProperty(n,"__esModule",{value:!0}),n.getSpecInfo=void 0;const t=a(88266);n.getSpecInfo=function(e){return s(this,void 0,void 0,function*(){return yield t.call({module:"bloks",api:"getSpecInfo",args:{styleId:e}})})}},88266(e,n){var a=this&&this.__awaiter||function(e,n,a,s){return new(a||(a=Promise))(function(t,i){function l(e){try{c(s.next(e))}catch(n){i(n)}}function r(e){try{c(s.throw(e))}catch(n){i(n)}}function c(e){var n;e.done?t(e.value):(n=e.value,n instanceof a?n:new a(function(e){e(n)})).then(l,r)}c((s=s.apply(e,n||[])).next())})};Object.defineProperty(n,"__esModule",{value:!0}),n.call=void 0;let s=!1,t=0;const i={};
1n.call=function(e){return a(this,void 0,void 0,function*(){if("staticdocs.thefacebook.com"!==window.location.hostname&&"localhost"!==window.location.hostname)return Promise.reject(new Error("Not running on static docs"));s||(s=!0,window.addEventListener("message",e=>{if("static-docs-bridge-response"!==e.data.event)return;const n=e.data.id;n in i||console.error(`Recieved response for id: ${n} with no matching receiver`),"response"in e.data?i[n].resolve(e.data.response):i[n].reject(new Error(e.data.error)),delete i[n]}));const n=t++,a=new Promise((e,a)=>{i[n]={resolve:e,reject:a}}),l={event:"static-docs-bridge-call",id:n,module:e.module,api:e.api,args:e.args},r="localhost"===window.location.hostname?"*":"https://www.internalfb.com";return window.parent.postMessage(l,r),a})}},70680(e,n,a){var s=this&&this.__awaiter||function(e,n,a,s){return new(a||(a=Promise))(function(t,i){function l(e){try{c(s.next(e))}catch(n){i(n)}}function r(e){try{c(s.throw(e))}catch(n){i(n)}}function c(e){var n;e.done?t(e.value):(n=e.value,n instanceof a?n:new a(function(e){e(n)})).then(l,r)}c((s=s.apply(e,n||[])).next())})};Object.defineProperty(n,"__esModule",{value:!0}),n.reportFeatureUsage=n.reportContentCopied=void 0;const t=a(88266);n.reportContentCopied=function(e){return s(this,void 0,void 0,function*(){const{textContent:n}=e;try{yield t.call({module:"feedback",api:"reportContentCopied",args:{textContent:n}})}catch(a){}})},n.reportFeatureUsage=function(e){return s(this,void 0,void 0,function*(){const{featureName:n,id:a}=e;console.log("used feature");try{yield t.call({module:"feedback",api:"reportFeatureUsage",args:{featureName:n,id:a}})}catch(s){}})}},14423(e,n,a){var s=this&&this.__createBinding||(Object.create?function(e,n,a,s){void 0===s&&(s=a),Object.defineProperty(e,s,{enumerable:!0,get:function(){return n[a]}})}:function(e,n,a,s){void 0===s&&(s=a),e[s]=n[a]}),t=this&&this.__setModuleDefault||(Object.create?function(e,n){Object.defineProperty(e,"default",{enumerable:!0,value:n})}:function(e,n){e.default=n}),i=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var n={};if(null!=e)for(var a in e)"default"!==a&&Object.prototype.hasOwnProperty.call(e,a)&&s(n,e,a);return t(n,e),n};Object.defineProperty(n,"__esModule",{value:!0}),n.OssOnly=n.FbInternalOnly=n.getEphemeralDiffNumber=n.hasEphemeralDiffNumber=n.isInternal=n.validateFbContentArgs=n.fbInternalOnly=n.fbContent=n.inpageeditor=n.feedback=n.uidocs=n.bloks=void 0,n.bloks=i(a(80510)),n.uidocs=i(a(3730)),n.feedback=i(a(70680)),n.inpageeditor=i(a(45458));const l=["internal","external"];function r(e){return d(e),o()?"internal"in e?c(e.internal):[]:"external"in e?c(e.external):[]}function c(e){return"function"==typeof e?e():e}function d(e){if("object"!=typeof e)throw new Error(`fbContent() args must be an object containing keys: ${l}. Instead got ${e}`);if(!Object.keys(e).find(e=>l.find(n=>n===e)))throw new Error(`No valid args found in ${JSON.stringify(e)}. Accepted keys: ${l}`);const n=Object.keys(e).filter(e=>!l.find(n=>n===e));if(n.length>0)throw new Error(`Unexpected keys ${n} found in fbContent() args. Accepted keys: ${l}`)}function o(){try{return Boolean(!1)}catch(e){return console.log("process.env.FB_INTERNAL couldn't be read, maybe you forgot to add the required webpack EnvironmentPlugin config?",e),!1}}function h(){try{return null}catch(e){return console.log("process.env.PHABRICATOR_DIFF_NUMBER couldn't be read, maybe you forgot to add the required webpack EnvironmentPlugin config?",e),null}}n.fbContent=r,n.fbInternalOnly=function(e){return r({internal:e})},n.validateFbContentArgs=d,n.isInternal=o,n.hasEphemeralDiffNumber=function(){return Boolean(h())},n.getEphemeralDiffNumber=h,n.FbInternalOnly=function(e){return o()?e.children:null},n.OssOnly=function(e){return o()?null:e.children}},45458(e,n,a){var s=this&&this.__awaiter||function(e,n,a,s){return new(a||(a=Promise))(function(t,i){function l(e){try{c(s.next(e))}catch(n){i(n)}}function r(e){try{c(s.throw(e))}catch(n){i(n)}}function c(e){var n;e.done?t(e.value):(n=e.value,n instanceof a?n:new a(function(e){e(n)})).then(l,r)}c((s=s.apply(e,n||[])).next())})};Object.defineProperty(n,"__esModule",{value:!0}),n.submitDiff=void 0;const t=a(88266);n.submitDiff=function(e){return s(this,void 0,void 0,function*(){const{file_path:n,new_content:a,project_name:s,diff_number:i}=e;try{return yield t.call({module:"inpageeditor",api:"createPhabricatorDiffApi",args:{file_path:n,new_content:a,project_name:s,diff_number:i}})}catch(l){throw new Error(`Error occurred while trying to submit diff. Stack trace: ${l}`)}})}},3730(e,n,a){var s=this&&this.__awaiter||function(e,n,a,s){return new(a||(a=Promise))(function(t,i){function l(e){try{c(s.next(e))}catch(n){i(n)}}function r(e){try{c(s.throw(e))}catch(n){i(n)}}function c(e){var n;e.done?t(e.value):(n=e.value,n instanceof a?n:new a(function(e){e(n)})).then(l,r)}c((s=s.apply(e,n||[])).next())})};Object.defineProperty(n,"__esModule",{value:!0}),n.getApi=n.docsets=void 0;const t=a(88266);n.docsets={BLOKS_CORE:"887372105406659"},n.getApi=function(e){return s(this,void 0,void 0,function*(){const{name:n,framework:a,docset:s}=e;return yield t.call({module:"uidocs",api:"getApi",args:{name:n,framework:a,docset:s}})})}},28453(e,n,a){a.d(n,{R:()=>l,x:()=>r});var s=a(96540);const t={},i=s.createContext(t);function l(e){const n=s.useContext(i);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:l(e.components),s.createElement(i.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.