How to make Mathematica variables declarative instead of just-in-time?

You are looking for $NewSymbol which is run every time a new symbol is created. For example, let say you only want x, y, and z as symbols, then declare them initially

In[63]:= {x, y, z}
(*Out[1]= {x, y, z}*)

Then, set $NewSymbol to issue a message when it is used, e.g.

In[2]:= $NewSymbol::undeclared = "`1` was not previously declared.";
In[3]:= $NewSymbol := Message[$NewSymbol::undeclared , #1] &

In[4]:= q
(*
During evaluation of In[4]:= $NewSymbol::undeclared: q was not previously declared.
Out[4]= q*)

But, no message is issued with x.

In[5]:= x = 5
(*Out[5]= 5*)

Additionally, you can create your own cell style that will treat variable declarations as expected using a custom CellEvaluationFunction

CellEvaluationFunction -> (Block[{$NewSymbol}, ToExpression[#]]&)

For instance, you can add it to the "Code" cell, e.g.

Cell[StyleData["Code"], 
 CellEvaluationFunction -> (Block[{$NewSymbol}, ToExpression[#]]&)
]

Edit: method extended for multiple contexts and unlocking mehtod added.


Let's protect whatever is a new symbol.

In old answer I've manually excluded symbols matching name$digits but that wasn't necessary as according to $NewSymbol details:

$NewSymbol is not applied to symbols automatically created by scoping constructs such as Module.

enter image description here

 BeginPackage["Lock`"];

    contextLock; contextUnlock;

 Begin["`Private`"];

   SetAttributes[{contextLock, contextUnlock}, HoldFirst];

   $LockedContexts = <||>;

   contextLock[context_: $Context] := Which[
     $LockedContexts === <||>
     , setLocking[]; $LockedContexts[context] = {}
     , Not@KeyExistsQ[$LockedContexts, context]
     , $LockedContexts[context] = {};
   ];



   contextUnlock[context_: $Context] /; KeyExistsQ[$LockedContexts, context] := (
       ToExpression[#, StandardForm, Unprotect] & /@ $LockedContexts[context]
     ; ToExpression[#, StandardForm, Remove] & /@ $LockedContexts[context]
     ; KeyDropFrom[$LockedContexts, context]
     ; If[$LockedContexts === <||>, $NewSymbol =.];
   )

   setLocking[] := $NewSymbol := If[
     MemberQ[Keys[$LockedContexts], #2]
     , AppendTo[$LockedContexts[#2], #2 <> #1]
     ; ToExpression[#2 <> #1, StandardForm, Protect]
   ] &;

End[];

EndPackage[];