taler-docs

Documentation for GNU Taler components, APIs and protocols
Log | Files | Refs | README | LICENSE

typescriptdomain.py (25872B)


      1 """
      2 TypeScript domain.
      3 
      4 :copyright: Copyright 2019 by Taler Systems SA
      5 :license: LGPLv3+
      6 :author: Florian Dold
      7 """
      8 
      9 import re
     10 
     11 from docutils import nodes
     12 from typing import Dict, Iterator, List, Tuple
     13 
     14 from pygments.filter import Filter
     15 from pygments.token import (
     16     Comment,
     17     Keyword,
     18     Name,
     19     Number,
     20     Operator,
     21     Punctuation,
     22     String,
     23     Text,
     24     Token,
     25     _TokenType,
     26 )
     27 from pygments.lexer import RegexLexer, bygroups, include
     28 from pygments.formatters import HtmlFormatter
     29 
     30 from docutils.nodes import Element, Node
     31 
     32 from sphinx.roles import XRefRole
     33 from sphinx import addnodes
     34 from sphinx.domains import Domain, ObjType
     35 from sphinx.directives import ObjectDescription, directives
     36 from sphinx.directives.code import (
     37     container_wrapper,
     38     dedent_lines,
     39     parse_line_num_spec,
     40 )
     41 from sphinx.locale import __
     42 from sphinx.util.docutils import SphinxDirective
     43 from sphinx.util.nodes import make_refnode
     44 from sphinx.util import logging
     45 from sphinx.highlighting import PygmentsBridge
     46 
     47 logger = logging.getLogger(__name__)
     48 
     49 
     50 class TypeScriptDefinition(SphinxDirective):
     51     """
     52     Directive for a code block with special highlighting or line numbering
     53     settings.
     54     """
     55 
     56     has_content = True
     57     required_arguments = 1
     58     optional_arguments = 0
     59     final_argument_whitespace = False
     60     option_spec = {
     61         "force": directives.flag,
     62         "linenos": directives.flag,
     63         "dedent": int,
     64         "lineno-start": int,
     65         "emphasize-lines": directives.unchanged_required,
     66         "caption": directives.unchanged_required,
     67         "class": directives.class_option,
     68     }
     69 
     70     def run(self) -> List[Node]:
     71         document = self.state.document
     72         code = "\n".join(self.content)
     73         location = self.state_machine.get_source_and_line(self.lineno)
     74 
     75         linespec = self.options.get("emphasize-lines")
     76         if linespec:
     77             try:
     78                 nlines = len(self.content)
     79                 hl_lines = parse_line_num_spec(linespec, nlines)
     80                 if any(i >= nlines for i in hl_lines):
     81                     logger.warning(
     82                         __("line number spec is out of range(1-%d): %r"),
     83                         nlines,
     84                         self.options["emphasize-lines"],
     85                         location=location,
     86                     )
     87 
     88                 hl_lines = [x + 1 for x in hl_lines if x < nlines]
     89             except ValueError as err:
     90                 return [document.reporter.warning(err, line=self.lineno)]
     91         else:
     92             hl_lines = None
     93 
     94         if "dedent" in self.options:
     95             location = self.state_machine.get_source_and_line(self.lineno)
     96             lines = code.splitlines(True)
     97             lines = dedent_lines(lines, self.options["dedent"], location=location)
     98             code = "".join(lines)
     99 
    100         literal = nodes.literal_block(code, code)  # type: Element
    101         if "linenos" in self.options or "lineno-start" in self.options:
    102             literal["linenos"] = True
    103         literal["classes"] += self.options.get("class", [])
    104         literal["force"] = "force" in self.options
    105         literal["language"] = "tsref"
    106         extra_args = literal["highlight_args"] = {}
    107         if hl_lines is not None:
    108             extra_args["hl_lines"] = hl_lines
    109         if "lineno-start" in self.options:
    110             extra_args["linenostart"] = self.options["lineno-start"]
    111         self.set_source_info(literal)
    112 
    113         caption = self.options.get("caption")
    114         if caption:
    115             try:
    116                 literal = container_wrapper(self, literal, caption)
    117             except ValueError as exc:
    118                 return [document.reporter.warning(exc, line=self.lineno)]
    119 
    120         tsid = "tsref-type-" + self.arguments[0]
    121         literal["ids"].append(tsid)
    122 
    123         tsname = self.arguments[0]
    124         ts = self.env.get_domain("ts")
    125         ts.add_object("type", tsname, self.env.docname, tsid)
    126 
    127         return [literal]
    128 
    129 
    130 class TypeScriptOperation(ObjectDescription):
    131     """
    132     Directive for an operation of an operation-based, non-HTTP API
    133     (such as the wallet-core client API).
    134 
    135     Renders like an endpoint from sphinxcontrib.httpdomain and registers
    136     the operation as a target for the ``ts:op`` cross-reference role:
    137 
    138     .. ts:op:: getBalances
    139       :read-only:
    140 
    141       Description of the operation ...
    142 
    143     Note that options and content must share the same indentation;
    144     docutils misreads an option line that is indented deeper than
    145     the content.
    146     """
    147 
    148     doc_field_types = []
    149 
    150     option_spec = {
    151         "read-only": directives.flag,
    152         "deprecated": directives.flag,
    153         "noindex": directives.flag,
    154     }
    155 
    156     index_label = "wallet-core operation"
    157 
    158     def handle_signature(self, sig, signode):
    159         signode += addnodes.desc_name(sig, sig)
    160         if "read-only" in self.options:
    161             signode += addnodes.desc_annotation("read-only", "read-only")
    162         if "deprecated" in self.options:
    163             signode += addnodes.desc_annotation("deprecated", "deprecated")
    164         signode["fullname"] = sig
    165         return sig
    166 
    167     def needs_arglist(self):
    168         return False
    169 
    170     def add_target_and_index(self, name, sig, signode):
    171         tsid = "tsref-op-" + name
    172         signode["ids"].append(tsid)
    173         ts = self.env.get_domain("ts")
    174         ts.add_object("op", name, self.env.docname, tsid)
    175         if "noindex" not in self.options:
    176             self.indexnode["entries"].append(
    177                 ("single", "%s (%s)" % (name, self.index_label), tsid, "", None)
    178             )
    179 
    180     def get_index_text(self, modname, name):
    181         return ""
    182 
    183 
    184 class TypeScriptDomain(Domain):
    185     """TypeScript domain."""
    186 
    187     name = "ts"
    188     label = "TypeScript"
    189     object_types = {
    190         "type": ObjType("type", "type"),
    191         "op": ObjType("op", "op"),
    192     }
    193     initial_data = {
    194         "objects": {},
    195     }
    196 
    197     directives = {
    198         "def": TypeScriptDefinition,
    199         "op": TypeScriptOperation,
    200     }
    201 
    202     roles = {
    203         "type": XRefRole(
    204             lowercase=False, warn_dangling=True, innernodeclass=nodes.inline
    205         ),
    206         "op": XRefRole(
    207             lowercase=False, warn_dangling=True, innernodeclass=nodes.inline
    208         ),
    209     }
    210 
    211     dangling_warnings = {
    212         "type": "undefined TypeScript type: %(target)s",
    213         "op": "undefined operation: %(target)s",
    214     }
    215 
    216     def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode):
    217         info = self.find_object(str(typ), str(target), fromdocname)
    218         if info is None:
    219             return None
    220         title = typ.upper() + " " + target
    221         return make_refnode(builder, fromdocname, info[0], info[1], contnode, title)
    222 
    223     def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode):
    224         """Resolve the pending_xref *node* with the given *target*.
    225 
    226         The reference comes from an "any" or similar role, which means that Sphinx
    227         don't know the type.
    228 
    229         For now sphinxcontrib-httpdomain doesn't resolve any xref nodes.
    230 
    231         :return:
    232            list of tuples ``('domain:role', newnode)``, where ``'domain:role'``
    233            is the name of a role that could have created the same reference,
    234         """
    235         ret = []
    236         info = self.find_object("type", str(target), fromdocname)
    237         if info is not None:
    238             title = "TYPE" + " " + target
    239             node = make_refnode(builder, fromdocname, info[0], info[1], contnode, title)
    240             ret.append(("ts:type", node))
    241         return ret
    242 
    243     @property
    244     def objects(self) -> Dict[Tuple[str, str], List[Tuple[str, str]]]:
    245         """Map ``(object type, name)`` to all documents defining it."""
    246 
    247         objects = self.data.setdefault("objects", {})
    248         # Environments written by the old extension stored just one tuple.
    249         for key, value in list(objects.items()):
    250             if isinstance(value, tuple):
    251                 objects[key] = [value]
    252         return objects
    253 
    254     def add_object(self, objtype: str, name: str, docname: str, labelid: str) -> None:
    255         locations = self.objects.setdefault((objtype, name), [])
    256         location = (docname, labelid)
    257         if location not in locations:
    258             locations.append(location)
    259 
    260     def find_object(
    261         self, objtype: str, name: str, fromdocname: str
    262     ) -> Tuple[str, str] | None:
    263         locations = self.objects.get((objtype, name), [])
    264         for location in locations:
    265             if location[0] == fromdocname:
    266                 return location
    267         if locations:
    268             return sorted(locations)[0]
    269         return None
    270 
    271     def clear_doc(self, docname: str) -> None:
    272         for key, locations in list(self.objects.items()):
    273             remaining = [location for location in locations if location[0] != docname]
    274             if remaining:
    275                 self.objects[key] = remaining
    276             else:
    277                 del self.objects[key]
    278 
    279     def merge_domaindata(self, docnames, otherdata) -> None:
    280         for (objtype, name), locations in otherdata.get("objects", {}).items():
    281             if isinstance(locations, tuple):
    282                 locations = [locations]
    283             for docname, labelid in locations:
    284                 if docname in docnames:
    285                     self.add_object(objtype, name, docname, labelid)
    286 
    287     def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]:
    288         for (objtype, name), locations in self.objects.items():
    289             for docname, labelid in locations:
    290                 yield name, name, objtype, docname, labelid, 1
    291 
    292 
    293 class BetterTypeScriptLexer(RegexLexer):
    294     """
    295     For `TypeScript <https://www.typescriptlang.org/>`_ source code.
    296     """
    297 
    298     name = "TypeScript"
    299     aliases = ["ts"]
    300     filenames = ["*.ts"]
    301     mimetypes = ["text/x-typescript"]
    302 
    303     flags = re.DOTALL
    304     tokens = {
    305         "commentsandwhitespace": [
    306             (r"\s+", Text),
    307             (r"<!--", Comment),
    308             (r"//.*?\n", Comment.Single),
    309             (r"/\*.*?\*/", Comment.Multiline),
    310         ],
    311         "slashstartsregex": [
    312             include("commentsandwhitespace"),
    313             (
    314                 r"/(\\.|[^[/\\\n]|\[(\\.|[^\]\\\n])*])+/" r"([gim]+\b|\B)",
    315                 String.Regex,
    316                 "#pop",
    317             ),
    318             (r"(?=/)", Text, ("#pop", "badregex")),
    319             (r"", Text, "#pop"),
    320         ],
    321         "badregex": [(r"\n", Text, "#pop")],
    322         "typeexp": [
    323             include("commentsandwhitespace"),
    324             (r"`(?:\\.|[^`])*`", String.Backtick),
    325             (r'"(\\\\|\\"|[^"])*"', String.Double),
    326             (r"'(\\\\|\\'|[^'])*'", String.Single),
    327             (r";", Punctuation, "#pop"),
    328             # Object-property names occur inside inline type literals.  Leave
    329             # those as ordinary names; their value type remains in this state.
    330             (r"[$a-zA-Z_][a-zA-Z0-9_$]*(?=\s*\??\s*:)", Name.Other),
    331             (r"[$a-zA-Z_][a-zA-Z0-9_$]*(?:\.[$a-zA-Z_][a-zA-Z0-9_$]*)*", Keyword.Type),
    332             (r"[{}()\[\],.?]", Punctuation),
    333             (r"[|&<>=:+*\-/]", Operator),
    334             (r"[0-9]+", Number.Integer),
    335             (r".", Text),
    336         ],
    337         "heritage": [
    338             include("commentsandwhitespace"),
    339             (r"{", Punctuation, "#pop"),
    340             (r"[$a-zA-Z_][a-zA-Z0-9_$]*(?:\.[$a-zA-Z_][a-zA-Z0-9_$]*)*", Keyword.Type),
    341             (r"[<>,.?\[\]&|]", Punctuation),
    342             (r".", Text),
    343         ],
    344         "root": [
    345             (r"^(?=\s|/|<!--)", Text, "slashstartsregex"),
    346             include("commentsandwhitespace"),
    347             # TypeScript template literal types and string templates.  Full
    348             # interpolation highlighting is unnecessary here, but recognizing
    349             # the complete literal avoids falling back to relaxed lexing.
    350             (r"`(?:\\.|[^`])*`", String.Backtick),
    351             # A reserved word can still be an object-property name (for
    352             # example ``class``).  Enter the type state when its colon arrives.
    353             (r"(:)(\s*)", bygroups(Text, Text), "typeexp"),
    354             # Resume a multi-line union/intersection after an inline object
    355             # member caused the type state to end at its semicolon.
    356             (r"([|&])(\s*)", bygroups(Operator, Text), "typeexp"),
    357             (
    358                 r"\+\+|--|~|&&|\?|:|\|\||\\(?=\n)|"
    359                 r"(<<|>>>?|==?|!=?|[-<>+*%&\|\^/])=?",
    360                 Operator,
    361                 "slashstartsregex",
    362             ),
    363             (r"[{(\[;,]", Punctuation, "slashstartsregex"),
    364             (r"[})\].]", Punctuation),
    365             (
    366                 r"(for|in|while|do|break|return|continue|switch|case|default|if|else|"
    367                 r"throw|try|catch|finally|new|delete|typeof|instanceof|void|"
    368                 r"this)\b",
    369                 Keyword,
    370                 "slashstartsregex",
    371             ),
    372             (
    373                 r"(var|let|const|with|function)\b",
    374                 Keyword.Declaration,
    375                 "slashstartsregex",
    376             ),
    377             (
    378                 r"(abstract|boolean|byte|char|class|const|debugger|double|enum|export|"
    379                 r"final|float|goto|import|int|interface|long|native|"
    380                 r"package|private|protected|public|short|static|super|synchronized|throws|"
    381                 r"transient|volatile)\b",
    382                 Keyword.Reserved,
    383             ),
    384             (r"(true|false|null|NaN|Infinity|undefined)\b", Keyword.Constant),
    385             (
    386                 r"(Array|Boolean|Date|Error|Function|Math|netscape|"
    387                 r"Number|Object|Packages|RegExp|String|sun|decodeURI|"
    388                 r"decodeURIComponent|encodeURI|encodeURIComponent|"
    389                 r"Error|eval|isFinite|isNaN|parseFloat|parseInt|document|this|"
    390                 r"window)\b",
    391                 Name.Builtin,
    392             ),
    393             # Match stuff like: module name {...}
    394             (
    395                 r"\b(module)(\s*)(\s*[a-zA-Z0-9_?.$][\w?.$]*)(\s*)",
    396                 bygroups(Keyword.Reserved, Text, Name.Other, Text),
    397                 "slashstartsregex",
    398             ),
    399             # Match variable type keywords
    400             (r"\b(string|bool|number)\b", Keyword.Type),
    401             # Match stuff like: constructor
    402             (r"\b(constructor|declare|interface|as|AS)\b", Keyword.Reserved),
    403             # Match interface/class heritage clauses.
    404             (
    405                 r"\b(extends|implements)(\s+)",
    406                 bygroups(Keyword.Reserved, Text),
    407                 "heritage",
    408             ),
    409             # Match stuff like: super(argument, list)
    410             (
    411                 r"(super)(\s*)\(([a-zA-Z0-9,_?.$\s]+\s*)\)",
    412                 bygroups(Keyword.Reserved, Text),
    413                 "slashstartsregex",
    414             ),
    415             # Match stuff like: function() {...}
    416             (r"([a-zA-Z_?.$][\w?.$]*)\(\) \{", Name.Other, "slashstartsregex"),
    417             # Match stuff like: (function: return type)
    418             (
    419                 r"([a-zA-Z0-9_?.$][\w?.$]*)(\s*:\s*)",
    420                 bygroups(Name.Other, Text),
    421                 "typeexp",
    422             ),
    423             # Match stuff like: type Foo = Bar | Baz
    424             (
    425                 r"\b(type)(\s+)([$a-zA-Z_][a-zA-Z0-9_$]*)([^=]*)(=)(\s*)",
    426                 bygroups(Keyword.Reserved, Text, Name.Other, Text, Operator, Text),
    427                 "typeexp",
    428             ),
    429             (r"[$a-zA-Z_][a-zA-Z0-9_]*", Name.Other),
    430             (r"[0-9][0-9]*\.[0-9]+([eE][0-9]+)?[fd]?", Number.Float),
    431             (r"0x[0-9a-fA-F]+", Number.Hex),
    432             (r"[0-9]+", Number.Integer),
    433             (r'"(\\\\|\\"|[^"])*"', String.Double),
    434             (r"'(\\\\|\\'|[^'])*'", String.Single),
    435         ],
    436     }
    437 
    438 
    439 # Map from token id to props.
    440 # Properties can't be added to tokens
    441 # since they derive from Python's tuple.
    442 token_props = {}
    443 
    444 
    445 class LinkFilter(Filter):
    446     def _filter_one_literal(self, ttype, value):
    447         last = 0
    448         for m in re.finditer(literal_reg, value):
    449             pre = value[last : m.start()]
    450             if pre:
    451                 yield ttype, pre
    452             t = copy_token(ttype)
    453             tok_setprop(t, "is_literal", True)
    454             yield t, m.group(1)
    455             last = m.end()
    456         post = value[last:]
    457         if post:
    458             yield ttype, post
    459 
    460     def filter(self, lexer, stream):
    461         for ttype, value in stream:
    462             if ttype in Token.Keyword.Type:
    463                 t = copy_token(ttype)
    464                 tok_setprop(t, "xref", value.strip())
    465                 tok_setprop(t, "is_identifier", True)
    466                 tok_setprop(t, "optional_xref", True)
    467                 yield t, value
    468             elif ttype in Token.Comment:
    469                 last = 0
    470                 for m in re.finditer(link_reg, value):
    471                     pre = value[last : m.start()]
    472                     if pre:
    473                         yield from self._filter_one_literal(ttype, pre)
    474                     t = copy_token(ttype)
    475                     x1, x2 = m.groups()
    476                     x0 = m.group(0)
    477                     if x2 is None:
    478                         caption = x1.strip()
    479                         xref = x1.strip()
    480                     else:
    481                         caption = x1.strip()
    482                         xref = x2.strip()
    483                     tok_setprop(t, "xref", xref)
    484                     tok_setprop(t, "caption", caption)
    485                     if x0.endswith("_"):
    486                         tok_setprop(t, "trailing_underscore", True)
    487                     elif x2 is None:
    488                         # A bare single-backtick span is also how Markdown/JSDoc
    489                         # writes inline code.  Link it when a target exists, but
    490                         # only diagnose explicit reStructuredText references.
    491                         tok_setprop(t, "optional_xref", True)
    492                     yield t, m.group(1)
    493                     last = m.end()
    494                 post = value[last:]
    495                 if post:
    496                     yield from self._filter_one_literal(ttype, post)
    497             else:
    498                 yield ttype, value
    499 
    500 
    501 _escape_html_table = {
    502     ord("&"): "&amp;",
    503     ord("<"): "&lt;",
    504     ord(">"): "&gt;",
    505     ord('"'): "&quot;",
    506     ord("'"): "&#39;",
    507 }
    508 
    509 
    510 class LinkingHtmlFormatter(HtmlFormatter):
    511     def __init__(self, **kwargs):
    512         super(LinkingHtmlFormatter, self).__init__(**kwargs)
    513         self._builder = kwargs["_builder"]
    514         self._bridge = kwargs["_bridge"]
    515 
    516     def _get_value(self, value, tok):
    517         xref = tok_getprop(tok, "xref")
    518         caption = tok_getprop(tok, "caption")
    519 
    520         if tok_getprop(tok, "is_literal"):
    521             return '<span style="font-weight: bolder">%s</span>' % (value,)
    522 
    523         if tok_getprop(tok, "trailing_underscore"):
    524             logger.warning(
    525                 "{}:{}: code block contains xref to '{}' with unsupported trailing underscore".format(
    526                     self._bridge.path, self._bridge.line, xref
    527                 )
    528             )
    529 
    530         if tok_getprop(tok, "is_identifier"):
    531             if not xref or xref.startswith('"'):
    532                 return value
    533             if re.match("^[0-9]+$", xref) is not None:
    534                 return value
    535 
    536         if self._bridge.docname is None:
    537             return value
    538         if xref is None:
    539             return value
    540         content = caption if caption is not None else value
    541         ts = self._builder.env.get_domain("ts")
    542         r1 = ts.find_object("type", xref, self._bridge.docname)
    543         # Qualified type references are currently one lexer token.  Link
    544         # ``Namespace.Member`` to the closest documented prefix if the member
    545         # itself is not registered as a standalone declaration.
    546         if r1 is None and tok_getprop(tok, "is_identifier") and "." in xref:
    547             parts = xref.split(".")
    548             for end in range(len(parts) - 1, 0, -1):
    549                 r1 = ts.find_object("type", ".".join(parts[:end]), self._bridge.docname)
    550                 if r1 is not None:
    551                     break
    552         if r1 is not None:
    553             rel_uri = (
    554                 self._builder.get_relative_uri(self._bridge.docname, r1[0])
    555                 + "#"
    556                 + r1[1]
    557             )
    558             return (
    559                 '<a style="color:inherit;text-decoration:underline" href="%s">%s</a>'
    560                 % (rel_uri, content)
    561             )
    562 
    563         if tok_getprop(tok, "is_identifier") and tok_getprop(tok, "optional_xref"):
    564             return value
    565 
    566         std = self._builder.env.get_domain("std")
    567         r2 = std.labels.get(xref.lower(), None)
    568         if r2 is not None:
    569             rel_uri = (
    570                 self._builder.get_relative_uri(self._bridge.docname, r2[0])
    571                 + "#"
    572                 + r2[1]
    573             )
    574             return (
    575                 '<a style="color:inherit;text-decoration:underline" href="%s">%s</a>'
    576                 % (rel_uri, content)
    577             )
    578         r3 = std.anonlabels.get(xref.lower(), None)
    579         if r3 is not None:
    580             rel_uri = (
    581                 self._builder.get_relative_uri(self._bridge.docname, r3[0])
    582                 + "#"
    583                 + r3[1]
    584             )
    585             return (
    586                 '<a style="color:inherit;text-decoration:underline" href="%s">%s</a>'
    587                 % (rel_uri, content)
    588             )
    589 
    590         if not tok_getprop(tok, "optional_xref"):
    591             logger.warning(
    592                 "{}:{}: code block contains unresolved xref '{}'".format(
    593                     self._bridge.path, self._bridge.line, xref
    594                 )
    595             )
    596 
    597         return value
    598 
    599     def _fmt(self, value, tok):
    600         cls = self._get_css_class(tok)
    601         value = self._get_value(value, tok)
    602         if cls is None or cls == "":
    603             return value
    604         return '<span class="%s">%s</span>' % (cls, value)
    605 
    606     def _format_lines(self, tokensource):
    607         """
    608         Just format the tokens, without any wrapping tags.
    609         Yield individual lines.
    610         """
    611         lsep = self.lineseparator
    612         escape_table = _escape_html_table
    613 
    614         line = ""
    615         for ttype, value in tokensource:
    616             parts = value.translate(escape_table).split("\n")
    617 
    618             if len(parts) == 0:
    619                 # empty token, usually should not happen
    620                 pass
    621             elif len(parts) == 1:
    622                 # no newline before or after token
    623                 line += self._fmt(parts[0], ttype)
    624             else:
    625                 line += self._fmt(parts[0], ttype)
    626                 yield 1, line + lsep
    627                 for part in parts[1:-1]:
    628                     yield 1, self._fmt(part, ttype) + lsep
    629                 line = self._fmt(parts[-1], ttype)
    630 
    631         if line:
    632             yield 1, line + lsep
    633 
    634 
    635 class LinkingPygmentsBridge(PygmentsBridge):
    636     def __init__(self, builder, style):
    637         self.dest = "html"
    638         self.latex_engine = None
    639         self.formatter_args = {
    640             "style": style,
    641             "_builder": builder,
    642             "_bridge": self,
    643         }
    644         self.formatter = LinkingHtmlFormatter
    645         self.builder = builder
    646         self.path = None
    647         self.line = None
    648         self.docname = None
    649 
    650     def highlight_block(
    651         self, source, lang, opts=None, force=False, location=None, **kwargs
    652     ):
    653         self.path = None
    654         self.line = None
    655         self.docname = None
    656         if isinstance(location, tuple):
    657             docname, line = location
    658             self.line = line
    659             self.path = self.builder.env.doc2path(docname)
    660             self.docname = docname
    661         elif isinstance(location, Element):
    662             self.line = location.line
    663             self.path = location.source
    664             self.docname = self.builder.env.path2doc(self.path)
    665         # The path/line above point at the source file the code block was
    666         # written in (used for diagnostics).  However, relative links must be
    667         # computed against the document that is *currently being written* --
    668         # not the (possibly ``.. include``-d) source file, which may live in a
    669         # deeper directory and would inject spurious "../" segments into every
    670         # cross reference.  Prefer the builder's current output docname.
    671         current = getattr(self.builder, "current_docname", None)
    672         if current:
    673             self.docname = current
    674         return super().highlight_block(source, lang, opts, force, location, **kwargs)
    675 
    676 
    677 def install_linking_highlighters(app):
    678     """Wrap the highlighters created by any standard HTML-family builder."""
    679 
    680     builder = app.builder
    681     if builder.format != "html":
    682         return
    683 
    684     def replace(highlighter):
    685         if highlighter is None:
    686             return None
    687         style = highlighter.formatter_args["style"]
    688         return LinkingPygmentsBridge(builder, style)
    689 
    690     builder.highlighter = replace(builder.highlighter)
    691     builder.dark_highlighter = replace(getattr(builder, "dark_highlighter", None))
    692 
    693 
    694 def copy_token(tok):
    695     new_tok = _TokenType(tok)
    696     # This part is very fragile against API changes ...
    697     new_tok.subtypes = set(tok.subtypes)
    698     new_tok.parent = tok.parent
    699     return new_tok
    700 
    701 
    702 def tok_setprop(tok, key, value):
    703     tokid = id(tok)
    704     e = token_props.get(tokid)
    705     if e is None:
    706         e = token_props[tokid] = (tok, {})
    707     _, kv = e
    708     kv[key] = value
    709 
    710 
    711 def tok_getprop(tok, key):
    712     tokid = id(tok)
    713     e = token_props.get(tokid)
    714     if e is None:
    715         return None
    716     _, kv = e
    717     return kv.get(key)
    718 
    719 
    720 link_reg = re.compile(r"(?<!`)`([^`<]+)\s*(?:<([^>]+)>)?\s*`_?")
    721 literal_reg = re.compile(r"``([^`]+)``")
    722 
    723 
    724 def setup(app):
    725 
    726     class TsrefLexer(BetterTypeScriptLexer):
    727         def __init__(self, **options):
    728             super().__init__(**options)
    729             self.add_filter(LinkFilter())
    730 
    731     app.add_lexer("tsref", TsrefLexer)
    732     app.add_domain(TypeScriptDomain)
    733     app.connect("builder-inited", install_linking_highlighters)
    734     return {
    735         "parallel_read_safe": True,
    736         "parallel_write_safe": True,
    737     }