Quais são as regras para inserção automática de ponto e vírgula (ASI) do JavaScript?

445

Bem, primeiro eu provavelmente deveria perguntar se isso depende do navegador.

Eu li que, se um token inválido for encontrado, mas a seção de código for válida até esse token inválido, um ponto-e-vírgula será inserido antes do token, se for precedido por uma quebra de linha.

No entanto, o exemplo comum citado para erros causados ​​pela inserção de ponto e vírgula é:

return
  _a+b;

..que parece não seguir esta regra, pois _a seria um token válido.

Por outro lado, dividir as cadeias de chamadas funciona conforme o esperado:

$('#myButton')
  .click(function(){alert("Hello!")});

Alguém tem uma descrição mais aprofundada das regras?

TR
fonte
22
Não é uma especificação ...
Miles
33
@Miles Apenas não em seu link quebrado ;-) ecma-international.org/publications/standards/Ecma-262.htm
Zach Lysobey
3
Veja a pág. 26 do PDF acima citado.
ᴠɪɴᴄᴇɴᴛ
consulte a seção 11.9 Inserção automática de ponto e vírgula
Andrew Lam

Respostas:

454

Antes de tudo, você deve saber quais instruções são afetadas pela inserção automática de ponto e vírgula (também conhecida como ASI por brevidade):

  • declaração vazia
  • var declaração
  • declaração de expressão
  • do-while declaração
  • continue declaração
  • break declaração
  • return declaração
  • throw declaração

As regras concretas do ASI estão descritas na especificação §11.9.1 Regras de inserção automática de ponto e vírgula

Três casos são descritos:

  1. Quando é encontrado um token ( LineTerminatorou }) que não é permitido pela gramática, um ponto-e-vírgula é inserido antes dele se:

    • O token é separado do token anterior por pelo menos um LineTerminator.
    • O token é }

    por exemplo :

    { 1
    2 } 3

    é transformado em

    { 1
    ;2 ;} 3;

    O NumericLiteral 1atende à primeira condição, o seguinte token é um terminador de linha.
    O 2atende à segunda condição, o seguinte token é }.

  2. Quando o final do fluxo de entrada de tokens é encontrado e o analisador não consegue analisar o fluxo de token de entrada como um único programa completo, um ponto-e-vírgula é inserido automaticamente no final do fluxo de entrada.

    por exemplo :

    a = b
    ++c

    é transformado em:

    a = b;
    ++c;
  3. Esse caso ocorre quando um token é permitido por alguma produção da gramática, mas a produção é restrita , um ponto-e-vírgula é inserido automaticamente antes do token restrito.

    Produções restritas:

    UpdateExpression :
        LeftHandSideExpression [no LineTerminator here] ++
        LeftHandSideExpression [no LineTerminator here] --
    
    ContinueStatement :
        continue ;
        continue [no LineTerminator here] LabelIdentifier ;
    
    BreakStatement :
        break ;
        break [no LineTerminator here] LabelIdentifier ;
    
    ReturnStatement :
        return ;
        return [no LineTerminator here] Expression ;
    
    ThrowStatement :
        throw [no LineTerminator here] Expression ; 
    
    ArrowFunction :
        ArrowParameters [no LineTerminator here] => ConciseBody
    
    YieldExpression :
        yield [no LineTerminator here] * AssignmentExpression
        yield [no LineTerminator here] AssignmentExpression

    O exemplo clássico, com o ReturnStatement:

    return 
      "something";

    é transformado em

    return;
      "something";
CMS
fonte
4
Nº 1: o token que não é permitido pela gramática geralmente não é um terminador de linha, é (a menos que você queira dizer as produções restritas do nº 3)? Acha que você deve omitir os parênteses. # 2 O exemplo não deve mostrar apenas a inserção depois ++cpara maior clareza?
Bergi
2
por favor note que ASI não precisa realmente "insert ponto e vírgula", apenas para terminar a instrução no analisador de um motor ...
Aprillion
1
o que diz "fluxo de entrada", isso significa "uma linha"? O "fluxo de token de entrada" está dificultando um pouco a compreensão
polaridade
O link de especificação funciona para mais alguém? Isso me levou a uma página quase vazia que tinha um link morto.
Intcreator 15/07/16
por favor, explique como, de acordo com essas regras, o exemplo abaixo de a 者 無極 而 生 de "a [LineBreak] = [LineBreak] 3" ainda funciona
Nir O.
45

Diretamente da especificação ECMA-262, Fifth Edition ECMAScript :

7.9.1 Regras de inserção automática de ponto e vírgula

Existem três regras básicas de inserção de ponto e vírgula:

  1. Quando, como o programa é analisado da esquerda para a direita, é encontrado um token (chamado token ofensivo ) que não é permitido por nenhuma produção da gramática, um ponto-e-vírgula é inserido automaticamente antes do token ofensivo, se um ou mais dos seguintes condições são verdadeiras:
    • O token incorreto é separado do token anterior por pelo menos um LineTerminator.
    • O token incorreto é }.
  2. Quando, como o programa é analisado da esquerda para a direita, o final do fluxo de tokens de entrada é encontrado e o analisador não consegue analisar o fluxo do token de entrada como um único ECMAScript completo Program, um ponto-e-vírgula é inserido automaticamente no final do fluxo de entrada.
  3. Quando, como o programa é analisado da esquerda para a direita, é encontrado um token permitido por alguma produção da gramática, mas a produção é restrita e o token seria o primeiro token para um terminal ou não terminal imediatamente após a anotação " [no LineTerminatorhere] " dentro da produção restrita (e, portanto, esse token é chamado de token restrito), e o token restrito é separado do token anterior por pelo menos um LineTerminator e , em seguida, um ponto e vírgula é inserido automaticamente antes do token restrito.

No entanto, há uma condição de substituição adicional nas regras anteriores: um ponto e vírgula nunca é inserido automaticamente se o ponto e vírgula for analisado como uma declaração vazia ou se esse ponto e vírgula se tornaria um dos dois pontos e vírgulas no cabeçalho de uma forinstrução (consulte 12.6 .3).

Jörg W Mittag
fonte
44

Eu não conseguia entender muito bem essas três regras nas especificações - espero ter algo mais claro em inglês -, mas aqui está o que coletei do JavaScript: The Definitive Guide, 6a Edição, David Flanagan, O'Reilly, 2011:

Citar:

O JavaScript não trata todas as quebras de linha como ponto-e-vírgula: normalmente trata as quebras de linha como ponto-e-vírgula somente se não puder analisar o código sem o ponto e vírgula.

Outra citação: para o código

var a
a
=
3 console.log(a)

O JavaScript não trata a segunda quebra de linha como ponto e vírgula, pois pode continuar analisando a instrução mais longa a = 3;

e:

duas exceções à regra geral de que o JavaScript interpreta quebras de linha como ponto-e-vírgula quando não puder analisar a segunda linha como uma continuação da instrução na primeira linha. A primeira exceção envolve as instruções de retorno, interrupção e continuação

... Se uma quebra de linha aparecer após qualquer uma dessas palavras ... O JavaScript sempre interpretará essa quebra de linha como ponto e vírgula.

... A segunda exceção envolve os operadores ++ e − ... Se você deseja usar um desses operadores como operadores postfix, eles devem aparecer na mesma linha que a expressão à qual se aplicam. Caso contrário, a quebra de linha será tratada como ponto e vírgula e o ++ ou - será analisado como um operador de prefixo aplicado ao código a seguir. Considere este código, por exemplo:

x 
++ 
y

É analisado como x; ++y;, não comox++; y

Então, acho que para simplificá-lo, isso significa:

Em geral, JavaScript irá tratá-lo como continuação de código enquanto faz sentido - exceto 2 casos: (1) depois de algumas palavras-chave como return, break, continue, e (2) se vê ++ou --em uma nova linha, em seguida, ele irá adicionar o ;no final da linha anterior.

A parte sobre "tratá-lo como continuação de código, desde que faça sentido" faz com que pareça a correspondência gananciosa da expressão regular.

Com o exposto acima, isso significa que, returncom uma quebra de linha, o intérprete JavaScript inserirá um;

(citado novamente: se uma quebra de linha aparecer após qualquer uma dessas palavras [como return] ... JavaScript sempre interpretará essa quebra de linha como ponto e vírgula)

e por esse motivo, o exemplo clássico de

return
{ 
  foo: 1
}

não funcionará conforme o esperado, porque o intérprete JavaScript o tratará como:

return;   // returning nothing
{
  foo: 1
}

Não deve haver quebra de linha imediatamente após o return:

return { 
  foo: 1
}

para que funcione corretamente. E você pode inserir um ;você mesmo se seguir a regra de usar um ;após qualquer instrução:

return { 
  foo: 1
};
falta de polaridade
fonte
17

Com relação à inserção de ponto-e-vírgula e à instrução var, lembre-se de esquecer a vírgula ao usar var, mas expandindo várias linhas. Alguém encontrou isso no meu código ontem:

    var srcRecords = src.records
        srcIds = [];

Ele foi executado, mas o efeito foi que a declaração / atribuição srcIds era global porque a declaração local com var na linha anterior não era mais aplicada, pois essa declaração foi considerada concluída devido à inserção automática de ponto e vírgula.

Dexygen
fonte
4
Esse tipo de coisa é porque eu uso JSLint
Zach Lysobey
1
JsHint / Lint direito no seu editor de código com resposta imediata :)
dmi3y
5
@balupton Quando a vírgula que terminaria a linha é esquecida, um ponto-e-vírgula é inserido automaticamente. Ao contrário de uma regra, era mais como uma "pegadinha".
Dexygen
1
Eu acho que Balupton está correto, é uma diferença se você escreve: var srcRecords = src.records srcIds = [];em uma linha e esquece a vírgula ou escreve "return a && b" e não esquece nada ... mas a quebra de linha antes do a insere um ponto-e-vírgula automático após o retorno, que é definido pelas regras ASI ...
Sebastian
3
Eu acho que a clareza de digitar var( let, const) em cada linha supera a fração de segundo que leva para digitá-la.
Squidbe 01/07/19
5

A descrição mais contextual da Inserção automática de ponto e vírgula do JavaScript que encontrei vem de um livro sobre intérpretes de artesanato .

A regra de "inserção automática de ponto e vírgula" do JavaScript é estranha. Onde outros idiomas assumem que a maioria das novas linhas são significativas e apenas algumas devem ser ignoradas nas instruções de várias linhas, JS assume o contrário. Ele trata todas as suas novas linhas como espaços em branco sem sentido, a menos que encontre um erro de análise. Caso isso aconteça, ele volta e tenta transformar a nova linha anterior em um ponto e vírgula para obter algo gramaticalmente válido.

Ele continua descrevendo como você codificaria o cheiro .

Esta nota de design se transformaria em um diatribe de design se eu fosse detalhadamente detalhado sobre como isso funciona, muito menos todas as várias maneiras pelas quais essa é uma má idéia. É uma bagunça. JavaScript é a única linguagem que conheço, onde muitos guias de estilo exigem ponto-e-vírgula explícito após cada declaração, embora a linguagem permita teoricamente excluí-los.

jchook
fonte